############################################################################## # # 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 Thu Sep 8 09:36:33 2016 @author: barasc """ import pandas as pd import numpy as np from app.models import Identite from app.pe import pe_affichage from app.pe.moys import pe_tabletags, pe_moy, pe_moytag, pe_sxtag from app.pe.rcss import pe_rcs import app.pe.pe_comp as pe_comp from app.scodoc.sco_utils import ModuleType class InterClassTag(pe_tabletags.TableTag): """ Interclasse l'ensemble des étudiants diplômés à une année donnée (celle du jury), pour un RCS donné (par ex: 'S2', '3S'), qu'il soit de type SemX ou RCSemX, en reportant les moyennes obtenues sur à la version tagguée du RCS (de type SxTag ou RCSTag). Sont ensuite calculés les classements (uniquement) sur les étudiants diplômes. Args: nom_rcs: Le nom de l'aggrégat type_interclassement: Le type d'interclassement (par UE ou par compétences) etudiants_diplomes: L'identité des étudiants diplômés rcss: Un dictionnaire {(nom_rcs, fid_final): RCS} donnant soit les SemX soit les RCSemX recencés par le jury PE rcstag: Un dictionnaire {(nom_rcs, fid_final): RCSTag} donnant soit les SxTag (associés aux SemX) soit les RCSTags (associés au RCSemX) calculés par le jury PE suivis: Un dictionnaire associé à chaque étudiant son rcss (de la forme ``{etudid: {nom_rcs: RCS_suivi}}``) """ def __init__( self, nom_rcs: str, type_interclassement: str, etudiants_diplomes: dict[int, Identite], rcss: dict[(str, int) : pe_rcs.RCS], rcstags: dict[(str, int) : pe_tabletags.TableTag], suivis: dict[int:dict], ): pe_tabletags.TableTag.__init__(self) self.nom_rcs: str = nom_rcs """Le nom du RCS interclassé""" # Le type d'interclassement self.type = type_interclassement pe_affichage.pe_print( f"*** Interclassement par 🗂️{type_interclassement} pour le RCS ⏯️{nom_rcs}" ) # Les informations sur les étudiants diplômés self.etuds: list[Identite] = list(etudiants_diplomes.values()) """Identités des étudiants diplômés""" self.add_etuds(self.etuds) self.diplomes_ids = set(etudiants_diplomes.keys()) """Etudids des étudiants diplômés""" # Les RCS de l'aggrégat (SemX ou RCSemX) self.rcss: dict[(str, int), pe_rcs.RCS] = {} """Ensemble des SemX ou des RCSemX associés à l'aggrégat""" for (nom, fid), rcs in rcss.items(): if nom == nom_rcs: self.rcss[(nom, fid)] = rcss # Les données tagguées self.rcstags: dict[(str, int), pe_tabletags.TableTag] = {} """Ensemble des SxTag ou des RCSTags associés à l'aggrégat""" for rcs_id in self.rcss: self.rcstags[rcs_id] = rcstags[rcs_id] # Les RCS (SemX ou RCSemX) suivis par les étudiants du jury, # en ne gardant que ceux associés aux diplomés self.suivis: dict[int, pe_rcs.RCS] = {} """Association entre chaque étudiant et le SxTag ou RCSTag à prendre pour l'aggrégat""" for etudid in self.diplomes_ids: self.suivis[etudid] = suivis[etudid][nom_rcs] # Les données sur les tags self.tags_sorted = self._do_taglist() """Liste des tags (triés par ordre alphabétique)""" aff = pe_affichage.repr_tags(self.tags_sorted) pe_affichage.pe_print(f"--> Tags : {aff}") # Les données sur les UEs (si SxTag) ou compétences (si RCSTag) self.champs_sorted = self._do_ues_ou_competences_list() """Les champs (UEs ou compétences) de l'interclassement""" if self.type == pe_moytag.CODE_MOY_UE: pe_affichage.pe_print( f"--> UEs : {pe_affichage.aff_UEs(self.champs_sorted)}" ) else: pe_affichage.pe_print( f"--> Compétences : {pe_affichage.aff_competences(self.champs_sorted)}" ) # Etudids triés self.etudids_sorted = sorted(list(self.diplomes_ids)) self.nom = self.get_repr() """Représentation textuelle de l'interclassement""" # Synthétise les moyennes/classements par tag self.moyennes_tags: dict[str, pe_moytag.MoyennesTag] = {} for tag in self.tags_sorted: # Les moyennes tous modules confondus notes_gen = self.compute_notes_matrice(tag) # Les coefficients de la moyenne générale coeffs = self.compute_coeffs_matrice(tag) aff = pe_affichage.repr_profil_coeffs(coeffs, with_index=True) pe_affichage.pe_print(f"--> Moyenne 👜{tag} avec coeffs: {aff} ") self.moyennes_tags[tag] = pe_moytag.MoyennesTag( tag, self.type, notes_gen, coeffs, # limite les moyennes aux étudiants de la promo ) def get_repr(self) -> str: """Une représentation textuelle""" return f"{self.nom_rcs} par {self.type}" def _do_taglist(self): """Synthétise les tags à partir des TableTags (SXTag ou RCSTag) Returns: Une liste de tags triés par ordre alphabétique """ tags = [] for rcstag in self.rcstags.values(): tags.extend(rcstag.tags_sorted) return sorted(set(tags)) def compute_notes_matrice(self, tag) -> pd.DataFrame: """Construit la matrice de notes (etudids x champs) en reportant les moyennes obtenues par les étudiants aux semestres de l'aggrégat pour le tag visé. Les champs peuvent être des acronymes d'UEs ou des compétences. Args: tag: Le tag visé Return: Le dataFrame (etudids x champs) reportant les moyennes des étudiants aux champs """ # etudids_sorted: Les etudids des étudiants (diplômés) triés # champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice # Partant d'un dataframe vierge df = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.champs_sorted) for rcstag in self.rcstags.values(): # Charge les moyennes au tag d'un RCStag if tag in rcstag.moyennes_tags: moytag = rcstag.moyennes_tags[tag] notes = moytag.matrice_notes_gen # dataframe etudids x ues # Etudiants/Champs communs entre le RCSTag et les données interclassées ( etudids_communs, champs_communs, ) = pe_comp.find_index_and_columns_communs(df, notes) # Injecte les notes par tag df.loc[etudids_communs, champs_communs] = notes.loc[ etudids_communs, champs_communs ] return df def compute_coeffs_matrice(self, tag) -> pd.DataFrame: """Idem que compute_notes_matrices mais pour les coeffs Args: tag: Le tag visé Return: Le dataFrame (etudids x champs) reportant les moyennes des étudiants aux champs """ # etudids_sorted: Les etudids des étudiants (diplômés) triés # champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice # Partant d'un dataframe vierge df = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.champs_sorted) for rcstag in self.rcstags.values(): if tag in rcstag.moyennes_tags: # Charge les coeffs au tag d'un RCStag coeffs: pd.DataFrame = rcstag.moyennes_tags[tag].matrice_coeffs_moy_gen # Etudiants/Champs communs entre le RCSTag et les données interclassées ( etudids_communs, champs_communs, ) = pe_comp.find_index_and_columns_communs(df, coeffs) # Injecte les coeffs par tag df.loc[etudids_communs, champs_communs] = coeffs.loc[ etudids_communs, champs_communs ] return df def _do_ues_ou_competences_list(self) -> list[str]: """Synthétise les champs (UEs ou compétences) sur lesquels sont calculés les moyennes. Returns: Un dictionnaire {'acronyme_ue' : 'compétences'} """ dict_champs = [] for rcstag in self.rcstags.values(): if isinstance(rcstag, pe_sxtag.SxTag): champs = rcstag.acronymes_sorted else: # pe_rcstag.RCSTag champs = rcstag.competences_sorted dict_champs.extend(champs) return sorted(set(dict_champs)) def has_tags(self): """Indique si l'interclassement a des tags (cas d'un interclassement sur un S5 qui n'a pas eu lieu) """ return len(self.tags_sorted) > 0 def _un_rcstag_significatif(self, rcsstags: dict[(str, int):pe_tabletags]): """Renvoie un rcstag significatif (ayant des tags et des notes aux tags) parmi le dictionnaire de rcsstags""" for rcstag_id, rcstag in rcsstags.items(): moystags: pe_moytag.MoyennesTag = rcstag.moyennes_tags for tag, moystag in moystags.items(): tags_tries = moystag.get_all_significant_tags() if tags_tries: return moystag return None def compute_df_synthese_moyennes_tag( self, tag, aggregat=None, type_colonnes=False, options={"min_max_moy": True} ) -> pd.DataFrame: """Construit le dataframe retraçant pour les données des moyennes pour affichage dans la synthèse du jury PE. (cf. to_df()) Args: etudids_sorted: Les etudids des étudiants (diplômés) triés champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice Return: Le dataFrame (etudids x champs) reportant les moyennes des étudiants aux champs """ if aggregat: assert ( aggregat == self.nom_rcs ), "L'interclassement ciblé ne correspond pas à l'aggrégat visé" etudids_sorted = sorted(list(self.diplomes_ids)) if not self.rcstags: return None # Partant d'un dataframe vierge initialisation = False df = pd.DataFrame() # Pour chaque rcs (suivi) associe la liste des etudids l'ayant suivi asso_rcs_etudids = {} for etudid in etudids_sorted: rcs = self.suivis[etudid] if rcs: if rcs.rcs_id not in asso_rcs_etudids: asso_rcs_etudids[rcs.rcs_id] = [] asso_rcs_etudids[rcs.rcs_id].append(etudid) for rcs_id, etudids in asso_rcs_etudids.items(): # Charge ses moyennes au RCSTag suivi rcstag = self.rcstags[rcs_id] # Le SxTag ou RCSTag # Charge la moyenne if tag in rcstag.moyennes_tags: moytag: pd.DataFrame = rcstag.moyennes_tags[tag] df_moytag = moytag.to_df( aggregat=aggregat, cohorte="Groupe", options=options ) # Modif les colonnes au regard du 1er df_moytag significatif lu if not initialisation: df = pd.DataFrame( np.nan, index=etudids_sorted, columns=df_moytag.columns ) colonnes = list(df_moytag.columns) for col in colonnes: if col.endswith("rang"): df[col] = df[col].astype(str) initialisation = True # Injecte les notes des étudiants df.loc[etudids, :] = df_moytag.loc[etudids, :] return df