# -*- 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 """ import pandas as pd from app import db, ScoValueError from app import comp from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_sem import load_formsemestre_results from app.models import FormSemestre, UniteEns from app.models.moduleimpls import ModuleImpl import app.pe.pe_affichage as pe_affichage import app.pe.pe_etudiant as pe_etudiant import app.pe.pe_tabletags as pe_tabletags from app.pe.pe_moytag import MoyennesTag from app.scodoc import sco_tag_module from app.scodoc import codes_cursus as sco_codes 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): """ Args: formsemestre: le ``FormSemestre`` sur lequel il se base """ ResultatsSemestreBUT.__init__(self, formsemestre) pe_tabletags.TableTag.__init__(self) # Le nom du res_semestre taggué self.nom = self.get_repr(verbose=True) pe_affichage.pe_print(f"--> ResultatsSemestreBUT taggués {self.nom}") # 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""" # 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 UEs en fonction des parcours self.ues_inscr_parcours_df = self.load_ues_inscr_parcours() """Les inscriptions des étudiants aux UEs du 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 = {} """L'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())) ) """Les compétences triées par nom""" # Les tags personnalisés et auto: tags_dict = self._get_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""" # 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 = {} """Les moyennes par tags (personnalisés ou 'but')""" for tag in tags_dict["personnalises"]: # pe_affichage.pe_print(f" -> Traitement du tag {tag}") infos_tag = tags_dict["personnalises"][tag] moy_ues_tag = self.compute_moy_ues_tag(infos_tag) self.moyennes_tags[tag] = MoyennesTag( tag, 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"] = MoyennesTag( "but", moy_gen, self.matrice_coeffs_moy_gen # , moy_gen_but ) self.tags_sorted = self.get_all_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, info_tag: dict[int, dict]) -> pd.DataFrame: """Calcule la moyenne par UE des étudiants pour un tag, en ayant connaissance des informations sur le tag. 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**. Returns: Le dataframe des moyennes du tag par UE """ # Adaptation du mask de calcul des moyennes au tag visé modimpls_mask = [ modimpl.module.ue.type == sco_codes.UE_STANDARD for modimpl in self.formsemestre.modimpls_sorted ] # Désactive tous les modules qui ne sont pas pris en compte pour ce tag for i, modimpl in enumerate(self.formsemestre.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() 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] # 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): """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()} # Les tags perso dict_tags["personnalises"] = get_synthese_tags_personnalises_semestre( self.formsemestre ) noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys()))) # 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": {}} noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp # Affichage pe_affichage.pe_print( f"* Tags du programme de formation : {', '.join(noms_tags_perso)} " + f"Tags automatiques : {', '.join(noms_tags_auto)}" ) 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 # "coeff": modimpl.module.coefficient, # le coeff du module dans le semestre "ponderation": ponderation, # la pondération demandée pour le tag sur le module # "module_code": modimpl.module.code, # le code qui doit se retrouver à l'identique dans des ue capitalisee # "ue_id": modimpl.module.ue.id, # les données sur l'ue # "ue_code": modimpl.module.ue.ue_code, # "ue_acronyme": modimpl.module.ue.acronyme, } return synthese_tags