From 99bb0f471b391ca0e3da80d1bcbbe300619603d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9o=20BARAS=20=28IUT1=20Grenoble=29?= Date: Wed, 6 Mar 2024 13:53:47 +0100 Subject: [PATCH] =?UTF-8?q?Moyenne=20avec=20UEs=20multiples=20pour=20une?= =?UTF-8?q?=20m=C3=AAme=20comp=20;=20Am=C3=A9lioration=20calcul=20coeffs?= =?UTF-8?q?=20+=20moyennes=20notamment=20pour=20d=C3=A9missionnaires?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/pe/moys/pe_rcstag.py | 328 ++++++++++++++---------------------- app/pe/moys/pe_ressemtag.py | 50 +++++- app/pe/moys/pe_sxtag.py | 22 ++- app/pe/pe_affichage.py | 43 +++-- app/pe/pe_comp.py | 18 ++ app/pe/pe_jury.py | 4 +- app/pe/rcss/pe_rcsemx.py | 2 +- 7 files changed, 239 insertions(+), 228 deletions(-) diff --git a/app/pe/moys/pe_rcstag.py b/app/pe/moys/pe_rcstag.py index cf135174..02e3d93f 100644 --- a/app/pe/moys/pe_rcstag.py +++ b/app/pe/moys/pe_rcstag.py @@ -119,7 +119,7 @@ class RCSemXTag(pe_tabletags.TableTag): ) """Compétences (triées par nom, extraites des SxTag aggrégés)""" aff = pe_affichage.repr_comp_et_ues(self.acronymes_ues_to_competences) - pe_affichage.pe_print(f"--> Compétences : {', '.join(self.competences_sorted)}") + pe_affichage.pe_print(f"--> Compétences : {aff}") # Les tags self.tags_sorted = self._do_taglist() @@ -134,28 +134,24 @@ class RCSemXTag(pe_tabletags.TableTag): for tag in self.tags_sorted: pe_affichage.pe_print(f"--> Moyennes du tag 👜{tag}") - # Traitement des inscriptions aux semX(tags) - # ****************************************** - # Cube d'inscription (etudids_sorted x compétences_sorted x sxstags) - # indiquant quel sxtag est valide pour chaque étudiant - inscr_df, inscr_cube = self.compute_inscriptions_comps_cube(tag) + # Cubes d'inscription (etudids_sorted x compétences_sorted x sxstags), + # de notes et de coeffs pour la moyenne générale + # en "aggrégant" les données des sxstags, compétence par compétence + ( + inscr_df, + inscr_cube, + notes_df, + notes_cube, + coeffs_df, + coeffs_cube, + ) = self.compute_cubes(tag) - # Traitement des notes - # ******************** - # Cube de notes (etudids_sorted x compétences_sorted x sxstags) - notes_df, notes_cube = self.compute_notes_comps_cube(tag) - # Calcule les moyennes sous forme d'un dataframe en les "aggrégant" - # compétence par compétence - moys_competences = self.compute_notes_competences(notes_cube, inscr_cube) - - # Traitement des coeffs pour la moyenne générale - # *********************************************** - # Df des coeffs sur tous les SxTags aggrégés - coeffs_df, coeffs_cube = self.compute_coeffs_comps_cube(tag) - - # Synthèse des coefficients à prendre en compte pour la moyenne générale - matrice_coeffs_moy_gen = self.compute_coeffs_competences( - coeffs_cube, inscr_cube, notes_cube + # Calcule les moyennes, et synthétise les coeffs + ( + moys_competences, + matrice_coeffs_moy_gen, + ) = self.compute_notes_et_coeffs_competences( + notes_cube, coeffs_cube, inscr_cube ) # Affichage des coeffs @@ -186,10 +182,17 @@ class RCSemXTag(pe_tabletags.TableTag): else: return f"{self.__class__.__name__} {self.rcs_id}" - def compute_notes_comps_cube(self, tag): - """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 + def compute_cubes(self, tag): + """Pour un tag donné, construit les cubes de : + * d'inscriptions aux compétences (etudid x competences x SxTag) + * de notes (etudid x competences x SxTag) + * de coeffs (etudid x competences x SxTag) + + nécessaire au calcul des moyennes, en : + + * transformant les données des UEs en données de compétences (changement de noms) + * fusionnant les données d'un même semestre, lorsque plusieurs UEs traitent d'une même compétence (cas des RCSx = Sx) + * aggrégeant les données de compétences sur plusieurs semestres (cas des RCSx = xA ou xS) Args: tag: Le tag visé @@ -197,144 +200,75 @@ class RCSemXTag(pe_tabletags.TableTag): # etudids_sorted: list[int], # competences_sorted: list[str], # sxstags: dict[(str, int) : pe_sxtag.SxTag], + + inscriptions_dfs = {} notes_dfs = {} + coeffs_dfs = {} for sxtag_id, sxtag in self.sxstags_aggreges.items(): - # Partant d'un dataframe vierge + # Partant de dataframes vierges + inscription_df = pd.DataFrame( + np.nan, index=self.etudids_sorted, columns=self.competences_sorted + ) notes_df = pd.DataFrame( np.nan, index=self.etudids_sorted, columns=self.competences_sorted ) - # Charge les notes du semestre tag (copie car changement de nom de colonnes à venir) + coeffs_df = pd.DataFrame( + np.nan, index=self.etudids_sorted, columns=self.competences_sorted + ) + + # Charge les données du semestre tag (copie car changement de nom de colonnes à venir) if tag in sxtag.moyennes_tags: # si le tag est présent dans le semestre moys_tag = sxtag.moyennes_tags[tag] - notes = moys_tag.matrice_notes_gen.copy() # dataframe etudids x ues + # Les inscr, les notes, les coeffs + acro_ues_inscr_parcours = sxtag.acro_ues_inscr_parcours + notes = moys_tag.matrice_notes_gen + coeffs = moys_tag.matrice_coeffs_moy_gen # les coeffs # Traduction des acronymes d'UE en compétences + # comp_to_ues = pe_comp.asso_comp_to_accronymes(self.acronymes_ues_to_competences) 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 + for acronyme in acronymes_ues_columns: + # La compétence visée + competence = self.acronymes_ues_to_competences[acronyme] # La comp - # Les étudiants et les compétences communes - ( - etudids_communs, - comp_communes, - ) = pe_comp.find_index_and_columns_communs(notes_df, notes) + # Les étud inscrits à la comp reportés dans l'inscription au RCSemX + comp_inscr = acro_ues_inscr_parcours[ + acro_ues_inscr_parcours.notnull() + ].index + etudids_communs = list( + inscription_df.index.intersection(comp_inscr) + ) + inscription_df.loc[ + etudids_communs, competence + ] = acro_ues_inscr_parcours.loc[etudids_communs, acronyme] - # Recopie des notes et des coeffs - notes_df.loc[etudids_communs, comp_communes] = notes.loc[ - etudids_communs, comp_communes - ] + # Les étud ayant une note à l'acronyme de la comp (donc à la comp) + etuds_avec_notes = notes[notes[acronyme].notnull()].index + etudids_communs = list( + notes_df.index.intersection(etuds_avec_notes) + ) + notes_df.loc[etudids_communs, competence] = notes.loc[ + etudids_communs, acronyme + ] + # Les coeffs + etuds_avec_coeffs = coeffs[coeffs[acronyme].notnull()].index + etudids_communs = list( + coeffs_df.index.intersection(etuds_avec_coeffs) + ) + coeffs_df.loc[etudids_communs, competence] = coeffs.loc[ + etudids_communs, acronyme + ] # 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 self.sxstags_aggreges - ] - 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): - """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: list[int], - # competences_sorted: list[str], - # sxstags: dict[(str, int) : pe_sxtag.SxTag], - - coeffs_dfs = {} - - for sxtag_id, sxtag in self.sxstags_aggreges.items(): - # Partant d'un dataframe vierge - coeffs_df = pd.DataFrame( - np.nan, index=self.etudids_sorted, columns=self.competences_sorted - ) - if tag in sxtag.moyennes_tags: - 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 - ] - - # Stocke les dfs - coeffs_dfs[sxtag_id] = coeffs_df - - """Réunit les coeffs sous forme d'un cube etudids x competences x semestres""" - sxtag_x_etudids_x_comps = [ - coeffs_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges - ] - coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1) - - return coeffs_dfs, coeffs_etudids_x_comps_x_sxtag - - def compute_inscriptions_comps_cube( - self, - tag, - ): - """Pour un tag donné, construit - le cube etudid x competences x SxTag traduisant quels sxtags est à prendre - en compte pour chaque étudiant. - Contient des 0 et des 1 pour indiquer la prise en compte. - - Args: - tag: Le tag visé - """ - # etudids_sorted: list[int], - # competences_sorted: list[str], - # sxstags: dict[(str, int) : pe_sxtag.SxTag], - # Initialisation - inscriptions_dfs = {} - - for sxtag_id, sxtag in self.sxstags_aggreges.items(): - # Partant d'un dataframe vierge - inscription_df = pd.DataFrame( - 0, index=self.etudids_sorted, columns=self.competences_sorted - ) - - # Les étudiants dont les résultats au sxtag ont été calculés - etudids_sxtag = sxtag.etudids_sorted - - # Les étudiants communs - etudids_communs = sorted(set(self.etudids_sorted) & set(etudids_sxtag)) - - # Acte l'inscription - inscription_df.loc[etudids_communs, :] = 1 - # Stocke les dfs inscriptions_dfs[sxtag_id] = inscription_df + notes_dfs[sxtag_id] = notes_df + coeffs_dfs[sxtag_id] = coeffs_df """Réunit les inscriptions sous forme d'un cube etudids x competences x semestres""" sxtag_x_etudids_x_comps = [ @@ -344,7 +278,26 @@ class RCSemXTag(pe_tabletags.TableTag): sxtag_x_etudids_x_comps, axis=-1 ) - return inscriptions_dfs, inscriptions_etudids_x_comps_x_sxtag + """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 self.sxstags_aggreges + ] + 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[sxtag_id] for sxtag_id in self.sxstags_aggreges + ] + coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1) + + return ( + inscriptions_dfs, + inscriptions_etudids_x_comps_x_sxtag, + notes_dfs, + notes_etudids_x_comps_x_sxtag, + coeffs_dfs, + coeffs_etudids_x_comps_x_sxtag, + ) def _do_taglist(self) -> list[str]: """Synthétise les tags à partir des Sxtags aggrégés. @@ -370,7 +323,9 @@ class RCSemXTag(pe_tabletags.TableTag): dict_competences |= sxtag.acronymes_ues_to_competences return dict_competences - def compute_notes_competences(self, set_cube: np.array, inscriptions: np.array): + def compute_notes_et_coeffs_competences( + self, notes_cube: np.array, coeffs_cube: np.array, inscr_mask: np.array + ): """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 @@ -379,9 +334,11 @@ class RCSemXTag(pe_tabletags.TableTag): par aggrégat de plusieurs semestres. Args: - set_cube: notes moyennes aux compétences ndarray + notes_cube: notes moyennes aux compétences ndarray (etuds x UEs|compétences x sxtags), des floats avec des NaN - inscriptions: inscrptions aux compétences ndarray + coeffs_cube: coeffs appliqués aux compétences + (etuds x UEs|compétences x sxtags), des floats avec des NaN + inscr_mask: inscrptions aux compétences ndarray (etuds x UEs|compétences x sxtags), des 0 et des 1 Returns: Un DataFrame avec pour columns les moyennes par tags, @@ -389,78 +346,45 @@ class RCSemXTag(pe_tabletags.TableTag): """ # etudids_sorted: liste des étudiants (dim. 0 du cube) # competences_sorted: list (dim. 1 du cube) - nb_etuds, nb_comps, nb_semestres = set_cube.shape + nb_etuds, nb_comps, nb_semestres = notes_cube.shape # assert nb_etuds == len(etudids_sorted) # assert nb_comps == len(competences_sorted) - # Applique le masque d'inscriptions - set_cube_significatif = set_cube * inscriptions + # Applique le masque d'inscriptions aux notes et aux coeffs + notes_significatives = notes_cube * inscr_mask + coeffs_significatifs = coeffs_cube * inscr_mask - # Quelles entrées du cube contiennent des notes ? - mask = ~np.isnan(set_cube_significatif) - - # Enlève les NaN du cube de notes pour les entrées manquantes - set_cube_no_nan = np.nan_to_num(set_cube_significatif, nan=0.0) + # Enlève les NaN des cubes pour les entrées manquantes + notes_no_nan = np.nan_to_num(notes_significatives, nan=0.0) + coeffs_no_nan = np.nan_to_num(coeffs_significatifs, 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) + mask = ~np.isnan( + notes_significatives + ) # Quelles entrées contiennent des notes ? + etud_moy_tag = np.sum(notes_no_nan, axis=2) / np.sum(mask, axis=2) + + coeffs_pris_en_compte = coeffs_no_nan * mask + coeff_tag = np.sum(coeffs_pris_en_compte, axis=2) + + inscr_prise_en_compte = inscr_mask * mask + inscr_prise_en_compte = np.nan_to_num(inscr_prise_en_compte, nan=-1.0) + inscr_tag = np.max(inscr_prise_en_compte, axis=2) + inscr_tag[inscr_tag < 0] = np.NaN # fix les max non calculés (-1) -> Na? # Le dataFrame des notes moyennes + etud_moy_tag = etud_moy_tag * inscr_tag etud_moy_tag_df = pd.DataFrame( etud_moy_tag, index=self.etudids_sorted, # les etudids columns=self.competences_sorted, # les competences ) - etud_moy_tag_df.fillna(np.nan) - - return etud_moy_tag_df - - def compute_coeffs_competences( - self, - coeff_cube: np.array, - inscriptions: np.array, - set_cube: np.array, - ): - """Calcule les coeffs à utiliser pour la moyenne générale (toutes compétences - confondues), en fonction des inscriptions. - - Args: - coeffs_cube: coeffs impliqués dans la moyenne générale (semestres par semestres) - inscriptions: inscriptions aux UES|Compétences ndarray - (etuds x UEs|compétences x sxtags), des 0 ou des 1 - set_cube: les notes - - - Returns: - Un DataFrame de coefficients (etudids_sorted x compétences_sorted) - """ - # etudids_sorted: liste des étudiants (dim. 0 du cube) - # competences_sorted: list (dim. 1 du cube) - nb_etuds, nb_comps, nb_semestres = inscriptions.shape - # assert nb_etuds == len(etudids_sorted) - # assert nb_comps == len(competences_sorted) - - # Applique le masque des inscriptions aux coeffs et aux notes - coeffs_significatifs = coeff_cube * inscriptions - - # Enlève les NaN du cube de notes pour les entrées manquantes - coeffs_cube_no_nan = np.nan_to_num(coeffs_significatifs, nan=0.0) - - # Quelles entrées du cube contiennent des notes ? - mask = ~np.isnan(set_cube) - - # Retire les coefficients associés à 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 + coeff_tag = coeff_tag * inscr_tag # Réapplique le masque des inscriptions coeffs_df = pd.DataFrame( coeff_tag, index=self.etudids_sorted, columns=self.competences_sorted ) - # Remet à Nan les coeffs à 0 - coeffs_df = coeffs_df.fillna(np.nan) - return coeffs_df + return etud_moy_tag_df, coeffs_df diff --git a/app/pe/moys/pe_ressemtag.py b/app/pe/moys/pe_ressemtag.py index cff92b94..0e9c9cc0 100644 --- a/app/pe/moys/pe_ressemtag.py +++ b/app/pe/moys/pe_ressemtag.py @@ -98,8 +98,10 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): self.parcours += [None] # Les UEs en fonction des parcours - self.ues_inscr_parcours_df = self.load_ues_inscr_parcours() - """Inscription des étudiants aux UEs des parcours""" + self.ues_inscr_parcours_df = ( + self.load_ues_inscr_parcours() + ) # peut contenir du sport + """Inscription des étudiants aux UEs des parcours (etudids x ue_ids)""" # Les acronymes des UEs self.ues_to_acronymes = {ue.id: ue.acronyme for ue in self.ues_standards} @@ -144,6 +146,12 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): f"--> Moyenne générale calculée avec pour coeffs d'UEs : {profils_aff}" ) + # Les inscriptions aux acronymes d'ues + self.acro_ues_inscr_parcours = self._get_acro_ues_inscr_parcours( + self.ues_inscr_parcours_df, self.ues_standards + ) + """DataFrame indiquant à quelles UEs (données par leurs acronymes) sont inscrits les étudiants)""" + # Les capitalisations (mask etuids x acronyme_ue valant True si capitalisée, False sinon) self.capitalisations = self._get_capitalisations(self.ues_standards) """DataFrame indiquant les UEs capitalisables d'un étudiant (etudids x )""" @@ -167,7 +175,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): ) # Ajoute les moyennes par UEs + la moyenne générale (but) - moy_gen = self.compute_moy_gen() + moy_gen = self.compute_moy_gen(self.acro_ues_inscr_parcours) self.moyennes_tags["but"] = pe_moytag.MoyennesTag( "but", pe_moytag.CODE_MOY_UE, @@ -240,6 +248,31 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): matrice_coeffs_moy_gen = matrice_coeffs_moy_gen.sort_index(axis=1) return matrice_coeffs_moy_gen + def _get_acro_ues_inscr_parcours( + self, ues_inscr_parcours_df: pd.DataFrame, ues_standards: list[UniteEns] + ) -> pd.DataFrame: + """Renvoie un dataFrame donnant les inscriptions (Nan ou 1) des + étudiants aux UEs définies par leur acronyme, en fonction de leur parcours + (cf. ues_inscr_parcours_df) et en limitant les données aux UEs standards (hors sport= + + Args: + 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 + """ + matrice_inscription = ues_inscr_parcours_df * [ + 1 for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé + ] + matrice_inscription.columns = [ + self.ues_to_acronymes[ue.id] for ue in ues_standards + ] + # Tri par etudids (dim 0) et par acronymes (dim 1) + matrice_inscription = matrice_inscription.sort_index() + matrice_inscription = matrice_inscription.sort_index(axis=1) + return matrice_inscription + 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). @@ -342,6 +375,9 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): colonnes = [ue.id for ue in self.ues_standards] moyennes_ues_tag = moyennes_ues_tag[colonnes] + # Met à zéro les moyennes non calculées/calculables + moyennes_ues_tag.fillna(0.0, inplace=True) + # Applique le masque d'inscription aux UE pour ne conserver que les UE dans lequel l'étudiant est inscrit moyennes_ues_tag = moyennes_ues_tag[colonnes] * ues_inscr_parcours_df[colonnes] @@ -355,7 +391,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): return moyennes_ues_tag - def compute_moy_gen(self): + def compute_moy_gen(self, acro_ues_inscr_parcours): """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) """ @@ -368,6 +404,12 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): acronymes = [self.ues_to_acronymes[col] for col in colonnes] df_ues.columns = acronymes + # Met à zéro les moyennes non calculées/calculables + df_ues.fillna(0.0, inplace=True) + + # Réapplique le mask d'inscription + df_ues = df_ues * acro_ues_inscr_parcours + # Tri par ordre aphabétique de colonnes df_ues.sort_index(axis=1) diff --git a/app/pe/moys/pe_sxtag.py b/app/pe/moys/pe_sxtag.py index 6ddb1ee9..3586362f 100644 --- a/app/pe/moys/pe_sxtag.py +++ b/app/pe/moys/pe_sxtag.py @@ -141,6 +141,10 @@ class SxTag(pe_tabletags.TableTag): aff = pe_affichage.repr_asso_ue_comp(self.acronymes_ues_to_competences) pe_affichage.pe_print(f"--> UEs/Compétences : {aff}") + # Les inscriptions des étudiants aux UEs (donnée par leur acronyme) + # par report de celle du ressemfinal + self.acro_ues_inscr_parcours = self.ressembuttag_final.acro_ues_inscr_parcours + # 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 @@ -178,7 +182,7 @@ class SxTag(pe_tabletags.TableTag): pe_affichage.pe_print(f" > MoyTag 👜{tag}") # Masque des inscriptions aux UEs (extraits de la matrice de coefficients) - inscr_mask: np.array = ~np.isnan(self.matrice_coeffs_moy_gen.to_numpy()) + # inscr_mask: np.array = ~np.isnan(self.matrice_coeffs_moy_gen.to_numpy()) # Moyennes (tous modules confondus) if not self.has_notes_tag(tag): @@ -194,6 +198,7 @@ class SxTag(pe_tabletags.TableTag): notes_df_gen, notes_cube_gen = self.compute_notes_ues_cube(tag) # DataFrame des moyennes (tous modules confondus) + inscr_mask = self.acro_ues_inscr_parcours.to_numpy() matrice_moys_ues = self.compute_notes_ues( notes_cube_gen, masque_cube, inscr_mask ) @@ -289,7 +294,7 @@ class SxTag(pe_tabletags.TableTag): def compute_notes_ues( self, set_cube: np.array, - masque_cube: np.array, + cap_mask_3D: np.array, inscr_mask: np.array, ) -> pd.DataFrame: """Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE @@ -298,7 +303,8 @@ class SxTag(pe_tabletags.TableTag): Args: set_cube: notes moyennes aux modules ndarray (semestre_ids x etudids x UEs), des floats avec des NaN - masque_cube: masque indiquant si la note doit être prise en compte ndarray + cap_mask_3D + : masque indiquant si la note doit être prise en compte ndarray (semestre_ids x etudids x UEs), des 1.0 ou des 0.0 inscr_mask: masque etudids x UE traduisant les inscriptions des étudiants aux UE (du semestre terminal) @@ -320,10 +326,7 @@ class SxTag(pe_tabletags.TableTag): set_cube = set_cube * inscr_mask_3D # Entrées à garder en fonction des UEs capitalisées ou non - set_cube = set_cube * masque_cube - - # Quelles entrées du cube contiennent des notes ? - mask = ~np.isnan(set_cube) + set_cube = set_cube * cap_mask_3D # Enlève les NaN du cube pour les entrées manquantes : NaN -> -1.0 set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0) @@ -332,9 +335,12 @@ class SxTag(pe_tabletags.TableTag): # TODO: Pour l'instant un max sans prise en compte des UE capitalisées etud_moy = np.max(set_cube_no_nan, axis=2) - # Fix les max non calculé -1 -> NaN + # Fix les max non calculés (-1) -> NaN etud_moy[etud_moy < 0] = np.NaN + # Réapplique le masque d'inscription (dans le doute) + etud_moy = etud_moy * inscr_mask + # Le dataFrame etud_moy_tag_df = pd.DataFrame( etud_moy, diff --git a/app/pe/pe_affichage.py b/app/pe/pe_affichage.py index 4ed7c7d3..a84bd372 100644 --- a/app/pe/pe_affichage.py +++ b/app/pe/pe_affichage.py @@ -9,6 +9,7 @@ from flask import g from app import log from app.pe.rcss import pe_rcs +import app.pe.pe_comp as pe_comp PE_DEBUG = False @@ -135,23 +136,45 @@ def aff_tags_par_categories(dict_tags): aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto]) return f"Tags automatiques {aff_tags_auto} (aucun tag personnalisé)" - # Affichage + +def repr_jeune(etudid, etudiants): + """Renvoie la représentation d'un étudiant""" + etat = "⛔" if etudid in etudiants.abandons_ids else "✅" + jeune = f"{etat} {etudiants.identites[etudid].nomprenom} (#{etudid})" + return jeune def aff_trajectoires_suivies_par_etudiants(etudiants): """Affiche les trajectoires (regroupement de (form)semestres) - amenant un étudiant du S1 à un semestre final""" + amenant un étudiant du S1 à un semestre final, + en regroupant les étudiants par profil de trajectoires""" + # Affichage pour debug etudiants_ids = etudiants.etudiants_ids jeunes = list(enumerate(etudiants_ids)) - for no_etud, etudid in jeunes: - etat = "⛔" if etudid in etudiants.abandons_ids else "✅" - pe_print(f"--> {etat} {etudiants.identites[etudid].nomprenom} (#{etudid}) :") + profils_traj = {} + + for no_etud, etudid in jeunes: + jeune = repr_jeune(etudid, etudiants) + + # La trajectoire du jeune trajectoires = etudiants.trajectoires[etudid] + profil_traj = [] for nom_rcs, rcs in trajectoires.items(): if rcs: - pe_print(f" > RCS ⏯️{nom_rcs}: {rcs.get_repr()}") + profil_traj += [f" > RCS ⏯️{nom_rcs}: {rcs.get_repr()}"] + aff_profil_traj = "\n".join(profil_traj) + if aff_profil_traj not in profils_traj: + profils_traj[aff_profil_traj] = [] + + profils_traj[aff_profil_traj] += [jeune] + + # Affichage final + for profil, jeunes in profils_traj.items(): + pe_print(f"--> Trajectoire suivie par : ") + pe_print("\n".join([" " + jeune for jeune in jeunes])) + pe_print(profil) def aff_semXs_suivis_par_etudiants(etudiants): @@ -198,13 +221,11 @@ def aff_capitalisations(etuds, ressembuttags, fid_final, acronymes_sorted, masqu def repr_comp_et_ues(acronymes_ues_to_competences): """Affichage pour debug""" + asso_comp_to_ues = pe_comp.asso_comp_to_accronymes(acronymes_ues_to_competences) aff_comp = [] - competences_sorted = sorted(acronymes_ues_to_competences.keys()) + competences_sorted = sorted(asso_comp_to_ues.keys()) for comp in competences_sorted: - liste = [] - for acro in acronymes_ues_to_competences: - if acronymes_ues_to_competences[acro] == comp: - liste += ["📍" + acro] + liste = ["📍" + accro for accro in asso_comp_to_ues[comp]] aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"] return "\n".join(aff_comp) diff --git a/app/pe/pe_comp.py b/app/pe/pe_comp.py index 0ab973f4..285a8154 100644 --- a/app/pe/pe_comp.py +++ b/app/pe/pe_comp.py @@ -337,3 +337,21 @@ def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSeme dernier_semestre = semestres[fid] return dernier_semestre return None + + +def asso_comp_to_accronymes(accro_ues_to_competences): + """Partant d'un dictionnaire ``{nom_ue: compétence}`` associant des + accronymes d'UEs à des compétences, renvoie l'association d'une compétence + à ou aux UEs qui l'adresse : ``{competence: [liste_nom_ue]}`` + + Args: + accro_ues_to_competences: Dictionnaire ``{nom_ue: compétence}`` + Return: + Le dictionnaire ``{competence: [liste_nom_ue]}`` + """ + asso = {} + for accro, comp in accro_ues_to_competences.items(): + if comp not in asso: + asso[comp] = [] + asso[comp].append(accro) + return asso diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index 103c8919..c5668f17 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -705,7 +705,7 @@ class JuryPE(object): tag, aggregat=aggregat, type_colonnes=False, options=self.options ) if not df_groupe.empty: - aff_aggregat += [aggregat] + aff_aggregat += [aggregat + " (Groupe)"] df = df.join(df_groupe) # Le dataframe du classement sur la promo @@ -718,7 +718,7 @@ class JuryPE(object): ) if not df_promo.empty: - aff_aggregat += [aggregat] + aff_aggregat += [aggregat + " (Promo)"] df = df.join(df_promo) if aff_aggregat: diff --git a/app/pe/rcss/pe_rcsemx.py b/app/pe/rcss/pe_rcsemx.py index e36596fb..c2198b9e 100644 --- a/app/pe/rcss/pe_rcsemx.py +++ b/app/pe/rcss/pe_rcsemx.py @@ -15,7 +15,7 @@ from app.pe.rcss import pe_rcs, pe_trajectoires class RCSemX(pe_rcs.RCS): - """Modélise un regroupement cohérent de SemX (en même regroupant + """Modélise un regroupement cohérent de SemX (en regroupant des semestres Sx combinés pour former les résultats des étudiants au semestre de rang x) dans le but de synthétiser les résultats du S1 jusqu'au semestre final ciblé par le RCSemX (dépendant de l'aggrégat