# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## ############################################################################## # Module "Avis de poursuite d'étude" # conçu et développé par Cléo Baras (IUT de Grenoble) ############################################################################## """ Created on Fri Sep 9 09:15:05 2016 @author: barasc """ 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 import app.pe.rcss.pe_rcs as pe_rcs import app.pe.rcss.pe_rcrcf as pe_rcrcf import app.pe.pe_sxtag as pe_sxtag import app.pe.pe_comp as pe_comp from app.pe.pe_tabletags import TableTag from app.pe.pe_moytag import MoyennesTag class RCSTag(TableTag): 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 groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous participé au même semestre terminal. Args: rcs: Un RCS (identifié par un nom et l'id de son semestre terminal) sxstags: Les données sur les RCF taggués """ TableTag.__init__(self) self.rcs_id: tuple(str, int) = rcrcf.rcs_id """Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)""" self.rcrcf: pe_rcrcf.RCRCF = rcrcf """RCRCF associé au RCS taggué""" self.nom = self.get_repr() """Représentation textuelle du RCS taggué""" # 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 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: for rcf_id in self.rcfs_aggreges: self.sxstags[rcf_id] = sxstags[rcf_id] except: raise ValueError("Semestres SxTag manquants") # Les étudiants (etuds, états civils & etudis) 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) """Les étudids triés""" # Les compétences (extraites de tous les Sxtags) 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() """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 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 ) def __eq__(self, other): """Egalité de 2 RCS taggués sur la base de leur identifiant""" return self.rcs_id == other.sxtag_id def get_repr(self, verbose=True) -> str: """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle est basée)""" if verbose: return self.rcrcf.get_repr(verbose=verbose) else: return f"{self.__class__.__name__} ({self.rcs_id})" 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 (dim 0) competences_sorted: Les compétences triées (dim 1) sxstags: Les SxTag à réunir """ notes_dfs = {} for sxtag_id, sxtag in sxstags.items(): # Partant d'un dataframe vierge 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] notes = moys_tag.matrice_notes.copy() # avec une copie # 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 ) # 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 ] # 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 sxstags] 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 _do_taglist(self) -> list[str]: """Synthétise les tags à partir des Sxtags aggrégés. Returns: 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 _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(): dict_competences |= sxtag.acronymes_ues_to_competences return dict_competences 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, etudids_sorted: list, competences_sorted: list, ): """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 *Remarque* : Adaptation de moy_ue.compute_ue_moys_apc au cas des moyennes de tag par aggrégat de plusieurs semestres. Args: 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 tags: liste des tags (dim. 1 du cube) Returns: Un DataFrame avec pour columns les moyennes par tags, et pour rows les etudid """ 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 set_cube_no_nan = np.nan_to_num(set_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) # Le dataFrame des notes moyennes etud_moy_tag_df = pd.DataFrame( etud_moy_tag, index=etudids_sorted, # les etudids columns=competences_sorted, # les competences ) etud_moy_tag_df.fillna(np.nan) return etud_moy_tag_df