# -*- pole: 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 Generfal 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 """ import pandas as pd from app import ScoValueError from app import comp from app.comp.res_but import ResultatsSemestreBUT from app.models import FormSemestre, UniteEns import app.pe.pe_affichage as pe_affichage import app.pe.pe_etudiant as pe_etudiant from app.pe.moys import pe_tabletags, pe_moytag from app.scodoc import sco_tag_module from app.scodoc import codes_cursus as sco_codes from app.scodoc.sco_utils import * 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 un ResultatsSemestreBUT. """ def __init__( self, formsemestre: FormSemestre, options={"moyennes_tags": True, "moyennes_ue_res_sae": False}, ): """ Args: formsemestre: le ``FormSemestre`` sur lequel il se base options: Un dictionnaire d'options """ ResultatsSemestreBUT.__init__(self, formsemestre) pe_tabletags.TableTag.__init__(self) # Le nom du res_semestre taggué self.nom = self.get_repr(verbose=True) # 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""" pe_affichage.pe_print( f"*** ResSemBUTTag du {self.nom} => {len(self.etudids_sorted)} étudiants" ) # Les UEs (et les dispenses d'UE) 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 parcours des étudiants à ce semestre self.parcours = [] """Parcours auxquels sont inscrits les étudiants""" for etudid in self.etudids_sorted: parcour = self.formsemestre.etuds_inscriptions[etudid].parcour if parcour: self.parcours += [parcour.libelle] else: 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""" # Les acronymes des UEs 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 = {} """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())) ) """Compétences triées par nom""" aff = pe_affichage.repr_asso_ue_comp(self.acronymes_ues_to_competences) pe_affichage.pe_print(f"--> UEs/Compétences : {aff}") # Les tags personnalisés et auto: if "moyennes_tags" in options: tags_dict = self._get_tags_dict(avec_moyennes_tags=options["moyennes_tags"]) else: tags_dict = self._get_tags_dict() pe_affichage.pe_print( f"""--> {pe_affichage.aff_tags_par_categories(tags_dict)}""" ) self._check_tags(tags_dict) # 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""" profils_aff = pe_affichage.repr_profil_coeffs(self.matrice_coeffs_moy_gen) pe_affichage.pe_print( f"--> Moyenne générale calculée avec pour coeffs d'UEs : {profils_aff}" ) # 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 )""" # Calcul des moyennes & les classements de chaque étudiant à chaque tag self.moyennes_tags = {} """Moyennes par tags (personnalisés ou 'but')""" for tag in tags_dict["personnalises"]: # pe_affichage.pe_print(f" -> Traitement du tag {tag}") info_tag = tags_dict["personnalises"][tag] # Les moyennes générales par UEs moy_ues_tag = self.compute_moy_ues_tag( self.ues_inscr_parcours_df, info_tag=info_tag, pole=None ) # Mémorise les moyennes self.moyennes_tags[tag] = pe_moytag.MoyennesTag( tag, pe_moytag.CODE_MOY_UE, moy_ues_tag, self.matrice_coeffs_moy_gen, ) # Ajoute les moyennes par UEs + la moyenne générale (but) moy_gen = self.compute_moy_gen() self.moyennes_tags["but"] = pe_moytag.MoyennesTag( "but", pe_moytag.CODE_MOY_UE, moy_gen, self.matrice_coeffs_moy_gen, ) # Ajoute la moyenne générale par ressources if "moyennes_ue_res_sae" in options and options["moyennes_ue_res_sae"]: moy_res_gen = self.compute_moy_ues_tag( self.ues_inscr_parcours_df, info_tag=None, pole=ModuleType.RESSOURCE ) self.moyennes_tags["ressources"] = pe_moytag.MoyennesTag( "ressources", pe_moytag.CODE_MOY_UE, moy_res_gen, self.matrice_coeffs_moy_gen, ) # Ajoute la moyenne générale par saes if "moyennes_ue_res_sae" in options and options["moyennes_ue_res_sae"]: moy_saes_gen = self.compute_moy_ues_tag( self.ues_inscr_parcours_df, info_tag=None, pole=ModuleType.SAE ) self.moyennes_tags["saes"] = pe_moytag.MoyennesTag( "saes", pe_moytag.CODE_MOY_UE, moy_saes_gen, self.matrice_coeffs_moy_gen, ) # Tous les tags self.tags_sorted = self.get_all_significant_tags() """Tags (personnalisés+compétences) par ordre alphabétique""" 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_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_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_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_standards: if ue.id in decisions and decisions[ue.id]["code"] == sco_codes.ADM: capitalisations.loc[etudid, ue.acronyme] = True # 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, ues_inscr_parcours_df: pd.DataFrame, info_tag: dict[int, dict] = None, pole=None, ) -> pd.DataFrame: """Calcule la moyenne par UE des étudiants pour un tag donné, en ayant connaissance des informations sur le tag et des inscriptions des étudiants aux différentes UEs. info_tag détermine les modules pris en compte : * si non `None`, seuls les modules rattachés au tag sont pris en compte * si `None`, tous les modules (quelque soit leur rattachement au tag) sont pris en compte (sert au calcul de la moyenne générale par ressource ou SAE) ues_inscr_parcours_df détermine les UEs pour lesquels le calcul d'une moyenne à un sens. `pole` détermine les modules pris en compte : * si `pole` vaut `ModuleType.RESSOURCE`, seules les ressources sont prises en compte (moyenne de ressources par UEs) * si `pole` vaut `ModuleType.SAE`, seules les SAEs sont prises en compte * si `pole` vaut `None` (ou toute autre valeur), tous les modules sont pris en compte (moyenne d'UEs) Les informations sur le tag sont un dictionnaire listant les modimpl_id rattachés au tag, et pour chacun leur éventuel coefficient de **repondération**. Args: ues_inscr_parcours_df: L'inscription aux UEs Returns: Le dataframe des moyennes du tag par UE """ modimpls_sorted = self.formsemestre.modimpls_sorted # Adaptation du mask de calcul des moyennes au tag visé modimpls_mask = [] for modimpl in modimpls_sorted: module = modimpl.module # Le module mask = module.ue.type == sco_codes.UE_STANDARD # Est-ce une UE stantard ? if pole == ModuleType.RESSOURCE: mask &= module.module_type == ModuleType.RESSOURCE elif pole == ModuleType.SAE: mask &= module.module_type == ModuleType.SAE modimpls_mask += [mask] # Prise en compte du tag if info_tag: # Désactive tous les modules qui ne sont pas pris en compte pour ce tag for i, modimpl in enumerate(modimpls_sorted): if modimpl.moduleimpl_id not in info_tag: modimpls_mask[i] = False # Applique la pondération des coefficients modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy() if info_tag: for modimpl_id in info_tag: ponderation = info_tag[modimpl_id]["ponderation"] modimpl_coefs_ponderes_df[modimpl_id] *= ponderation # Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues) moyennes_ues_tag = comp.moy_ue.compute_ue_moys_apc( self.sem_cube, self.etuds, self.formsemestre.modimpls_sorted, self.modimpl_inscr_df, modimpl_coefs_ponderes_df, modimpls_mask, self.dispense_ues, 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] # 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] # Transforme les UEs en acronyme 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, avec_moyennes_tags=True): """Renvoie les tags personnalisés (déduits des modules du semestre) et les tags automatiques ('but'), et toutes leurs informations, dans un dictionnaire de la forme : ``{"personnalises": {tag: info_sur_le_tag}, "auto": {tag: {}}`` Returns: Le dictionnaire structuré des tags ("personnalises" vs. "auto") """ dict_tags = {"personnalises": dict(), "auto": dict()} if avec_moyennes_tags: # Les tags perso (seulement si l'option d'utiliser les tags perso est choisie) dict_tags["personnalises"] = get_synthese_tags_personnalises_semestre( self.formsemestre ) # Les tags automatiques # Déduit des compétences # dict_ues_competences = get_noms_competences_from_ues(self.nt.formsemestre) # noms_tags_comp = list(set(dict_ues_competences.values())) # BUT dict_tags["auto"] = {"but": {}, "ressources": {}, "saes": {}} return dict_tags def _check_tags(self, dict_tags): """Vérifie l'unicité des tags""" noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys()))) noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp noms_tags = noms_tags_perso + noms_tags_auto intersection = list(set(noms_tags_perso) & set(noms_tags_auto)) if intersection: liste_intersection = "\n".join( [f"
  • {tag}
  • " for tag in intersection] ) s = "s" if len(intersection) > 1 else "" message = f"""Erreur dans le module PE : Un des tags saisis dans votre programme de formation fait parti des tags réservés. En particulier, votre semestre {self.formsemestre.titre_annee()} contient le{s} tag{s} réservé{s} suivant : Modifiez votre programme de formation pour le{s} supprimer. Il{s} ser{'ont' if s else 'a'} automatiquement à vos documents de poursuites d'études. """ raise ScoValueError(message) def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre): """Etant données les implémentations des modules du semestre (modimpls), synthétise les tags renseignés dans le programme pédagogique & associés aux modules du semestre, en les associant aux modimpls qui les concernent (modimpl_id) et au coeff de repondération fournie avec le tag (par défaut 1 si non indiquée)). Le dictionnaire fournit est de la forme : ``{ tag : { modimplid: {"modimpl": ModImpl, "ponderation": coeff_de_reponderation} } }`` Args: formsemestre: Le formsemestre à la base de la recherche des tags Return: Un dictionnaire décrivant les tags """ synthese_tags = {} # Instance des modules du semestre modimpls = formsemestre.modimpls_sorted for modimpl in modimpls: modimpl_id = modimpl.id # Liste des tags pour le module concerné tags = sco_tag_module.module_tag_list(modimpl.module.id) # Traitement des tags recensés, chacun pouvant étant de la forme # "mathématiques", "théorie", "pe:0", "maths:2" for tag in tags: # Extraction du nom du tag et du coeff de pondération (tagname, ponderation) = sco_tag_module.split_tagname_coeff(tag) # Ajout d'une clé pour le tag if tagname not in synthese_tags: synthese_tags[tagname] = {} # Ajout du module (modimpl) au tagname considéré synthese_tags[tagname][modimpl_id] = { "modimpl": modimpl, # les données sur le module "ponderation": ponderation, # la pondération demandée pour le tag sur le module } return synthese_tags