# -*- 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 UniteEns from app.pe import pe_affichage from app.pe.pe_ressemtag import ResSemTag import pandas as pd import numpy as np from app.pe.pe_rcs import RCS from app.pe.pe_tabletags import TableTag from app.pe.pe_moytag import MoyennesTag class SemTag(TableTag): def __init__(self, rcs: RCS, semestres_taggues: dict[int, ResSemTag]): """Calcule les moyennes/classements par tag à un RCS d'un seul semestre (ici semestre) de type 'Sx' (par ex. 'S1', 'S2', ...) : * pour les étudiants non redoublants, ce sont les moyennes/classements du semestre suivi * pour les étudiants redoublants, c'est une fusion des moyennes/classements suivis les différents 'Sx' (donné par dans le rcs) Les **tags considérés** sont uniquement ceux du dernier semestre du RCS Args: rcs: Un RCS (identifié par un nom et l'id de son semestre terminal) semestres_taggues: Les données sur les semestres taggués """ TableTag.__init__(self) self.rcs_id = rcs.rcs_id """Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)""" self.rcs = rcs """RCS associé au RCS taggué""" assert self.rcs.nom.startswith( "S" ), "Un SemTag ne peut être utilisé que pour un RCS de la forme Sx" self.nom = self.get_repr() """Représentation textuelle du RCS taggué""" # Les données du formsemestre_terminal self.formsemestre_terminal = rcs.formsemestre_final """Le formsemestre terminal""" # Les résultats du formsemestre terminal nt = load_formsemestre_results(self.formsemestre_terminal) self.semestres_aggreges = rcs.semestres_aggreges """Les semestres aggrégés""" self.semestres_tags_aggreges = {} """Les semestres tags associés aux semestres aggrégés""" try: for frmsem_id in self.semestres_aggreges: self.semestres_tags_aggreges[frmsem_id] = semestres_taggues[frmsem_id] except: raise ValueError("Semestres taggués manquants") # Les données des étudiants self.etuds = nt.etuds """Les étudiants""" self.etudids = [etud.etudid for etud in self.etuds] """Les etudids""" self.etats_civils = { etudid: self.etuds[etudid].etat_civil for etudid in self.etudids } """Les états civils""" # Les tags self.tags_sorted = self.comp_tags_list() """Tags extraits du semestre terminal de l'aggrégat""" # Les UEs self.ues = self.comp_ues(tag="but") self.acronymes_ues_sorted = sorted([ue.acronyme for ue in self.ues.values()]) """UEs extraites du semestre terminal de l'aggrégat (avec check de concordance sur les UE des semestres_aggrégés)""" self.moyennes_tags: dict[str, MoyennesTag] = {} """Moyennes/classements par tag (qu'ils soient personnalisés ou automatiques)""" self.notes: dict[str, pd.DataFrame] = {} """Les notes aux différents tags""" for tag in self.tags_sorted: # Cube de note notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues) # Calcule des moyennes sous forme d'un dataframe""" self.notes[tag] = compute_notes_ues(notes_cube, self.etudids, self.acronymes_ues) # Les moyennes self.moyennes_tags[tag] = MoyennesTag(tag, self.notes[tag]) def __eq__(self, other): """Egalité de 2 RCS taggués sur la base de leur identifiant""" return self.rcs_id == other.rcs_id def get_repr(self, verbose=False) -> str: """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle est basée)""" return self.rcs.get_repr(verbose=verbose) def compute_notes_ues_cube(self, tag, acronymes_ues_sorted): """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 """ # Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2) etudids = [etud.etudid for etud in self.etuds] # acronymes_ues = sorted([ue.acronyme for ue in self.ues.values()]) semestres_id = list(self.semestres_tags_aggreges.keys()) dfs = {} for frmsem_id in semestres_id: # Partant d'un dataframe vierge df = pd.DataFrame(np.nan, index=etudids, columns=acronymes_ues_sorted) # Charge les notes du semestre tag sem_tag = self.semestres_tags_aggreges[frmsem_id] moys_tag = sem_tag.moyennes_tags[tag] notes = moys_tag.notes_ues # dataframe etudids x ues acronymes_ues_sem = list(notes.columns) # les acronymes des UEs du semestre tag # UEs communes à celles du SemTag (celles du dernier semestre du RCS) ues_communes = list(set(acronymes_ues_sorted) & set(acronymes_ues_sem)) # Etudiants communs etudids_communs = df.index.intersection(notes.index) # Recopie df.loc[etudids_communs, ues_communes] = notes.loc[etudids_communs, ues_communes] # Supprime tout ce qui n'est pas numérique for col in df.columns: df[col] = pd.to_numeric(df[col], errors="coerce") # Stocke le df dfs[frmsem_id] = df """Réunit les notes sous forme d'un cube semestres x etdids x ues""" semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs] etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1) return etudids_x_ues_x_semestres def comp_tags_list(self): """Récupère les tag du semestre taggué associé au semestre final du RCS Returns: Une liste de tags triés par ordre alphabétique """ tags = [] dernier_frmid = self.formsemestre_terminal.formsemestre_id dernier_semestre_tag = self.semestres_tags_aggreges[dernier_frmid] tags = dernier_semestre_tag.tags_sorted pe_affichage.pe_print(f"* Tags : {', '.join(tags)}") return tags def comp_ues(self, tag="but") -> dict[int, UniteEns]: """Récupère les UEs à aggréger, en s'appuyant sur la moyenne générale (tag but) du semestre final du RCS Returns: Un dictionnaire donnant les UEs """ dernier_frmid = self.formsemestre_terminal.formsemestre_id dernier_semestre_tag = self.semestres_tags_aggreges[dernier_frmid] moy_tag = dernier_semestre_tag.moyennes_tags[tag] return moy_tag.ues # les UEs def compute_notes_ues(set_cube: np.array, etudids: list, acronymes_ues: list): """Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE par UE) obtenue par un étudiant à un semestre. Args: set_cube: notes moyennes aux modules ndarray (semestre_ids x etudids x UEs), des floats avec des NaN etudids: liste des étudiants (dim. 0 du cube) acronymes_ues: liste des acronymes des ues (dim. 1 du cube) Returns: Un DataFrame avec pour columns les moyennes par ues, et pour rows les etudid """ nb_etuds, nb_ues, nb_semestres = set_cube.shape assert nb_etuds == len(etudids) assert nb_ues == len(acronymes_ues) # Quelles entrées du cube contiennent des notes ? mask = ~np.isnan(set_cube) # Enlève les NaN du cube pour les entrées manquantes set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0) # Les moyennes par ues # 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 etud_moy[etud_moy < 0] = np.NaN # Le dataFrame etud_moy_tag_df = pd.DataFrame( etud_moy, index=etudids, # les etudids columns=acronymes_ues, # les tags ) etud_moy_tag_df.fillna(np.nan) return etud_moy_tag_df