# -*- 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 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 FormSemestre et sur 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"--> Résultats de semestre taggués {self.nom}") # Les étudiants (etuds, états civils & etudis) ajouté self.add_etuds(self.etuds) self.etudids_sorted = sorted(self.etudids) # Les UEs (et les dispenses d'UE) # self.ues ues_standards = [ue for ue in self.ues if ue.type == sco_codes.UE_STANDARD] # Les UEs en fonction des parcours self.ues_inscr_parcours_df = self.load_ues_inscr_parcours() # Les compétences associées aux UEs (définies par les acronymes) self.competences = {} """L'association acronyme d'UEs -> compétence""" for ue in self.ues: if ue.type == sco_codes.UE_STANDARD: assert ue.niveau_competence, ScoValueError( "Des UEs ne sont pas rattachées à des compétences" ) nom = ue.niveau_competence.competence.titre self.competences[ue.acronyme] = nom # Les acronymes des UEs self.ues_to_acronymes = {ue.id: ue.acronyme for ue in ues_standards} self.acronymes_sorted = sorted(self.ues_to_acronymes.values()) """Les acronymes de UE triés par ordre alphabétique""" # 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 self.matrice_coeffs_moy_gen = self.ues_inscr_parcours_df * [ ue.ects for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé ] # Les capitalisations (mask etuids x acronyme_ue valant True si capitalisée, False sinon) self.capitalisations = self._get_capitalisations(ues_standards) # Calcul des moyennes & les classements de chaque étudiant à chaque tag self.moyennes_tags = {} 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) df_ues = pd.DataFrame( {ue.id: self.etud_moy_ue[ue.id] for ue in 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 self.moyennes_tags["but"] = MoyennesTag( "but", df_ues, 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): """Nom affiché pour le semestre taggué""" if verbose: return f"{self.formsemestre} (#{self.formsemestre.formsemestre_id})" else: return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True) def _get_capitalisations(self, ues_hors_sport) -> pd.DataFrame: """Renvoie un dataFrame résumant les UEs capitalisables par les étudiants, d'après les décisions de jury Args: ues_hors_sport: Liste des UEs autres que le sport """ 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_hors_sport: if ue.id in decisions and decisions[ue.id]["code"] == sco_codes.ADM: capitalisations.loc[etudid, ue.acronyme] = True # pe_affichage.pe_print( # f" ⚠ Capitalisation de {ue.acronyme} pour {etud.etat_civil}" # ) 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, ) # Transforme les UEs en acronyme colonnes = moyennes_ues_tag.columns ue_to_acro = {ue.id: ue.acronyme for ue in self.ues} acronymes = [ue_to_acro[col] for col in colonnes] moyennes_ues_tag.columns = acronymes return moyennes_ues_tag 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