diff --git a/app/pe/pe_affichage.py b/app/pe/pe_affichage.py index 484ae5da..e66934f4 100644 --- a/app/pe/pe_affichage.py +++ b/app/pe/pe_affichage.py @@ -1,5 +1,16 @@ from app.models import Formation, FormSemestre from app.scodoc import codes_cursus +from app import log + +PE_DEBUG = 0 + +if not PE_DEBUG: + # log to notes.log + def pe_print(*a, **kw): + # kw is ignored. log always add a newline + log(" ".join(a)) +else: + pe_print = print # print function def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str: @@ -29,7 +40,7 @@ def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str: f"{semestre.date_debut.year}-{semestre.date_fin.year}", ] if avec_fid: - description.append(f"({semestre.forsemestre_id})") + description.append(f"({semestre.formsemestre_id})") return " ".join(description) diff --git a/app/pe/pe_comp.py b/app/pe/pe_comp.py index bbdd2a54..f65057fe 100644 --- a/app/pe/pe_comp.py +++ b/app/pe/pe_comp.py @@ -45,21 +45,11 @@ import unicodedata from flask import g import app.scodoc.sco_utils as scu -from app import log + from app.models import FormSemestre from app.scodoc import sco_formsemestre from app.scodoc.sco_logos import find_logo -PE_DEBUG = 0 - -if not PE_DEBUG: - # log to notes.log - def pe_print(*a, **kw): - # kw is ignored. log always add a newline - log(" ".join(a)) - -else: - pe_print = print # print function # Generated LaTeX files are encoded as: diff --git a/app/pe/pe_etudiant.py b/app/pe/pe_etudiant.py index f901455c..e488fe06 100644 --- a/app/pe/pe_etudiant.py +++ b/app/pe/pe_etudiant.py @@ -35,10 +35,9 @@ Created on 17/01/2024 @author: barasc """ - -import app.pe.pe_comp as pe_comp from app.models import FormSemestre, Identite -from app.pe.pe_comp import pe_print +import app.pe.pe_affichage as pe_affichage +import app.pe.pe_comp as pe_comp class EtudiantsJuryPE: @@ -86,17 +85,17 @@ class EtudiantsJuryPE: cosemestres = pe_comp.get_cosemestres_diplomants(self.annee_diplome, None) self.cosemestres = cosemestres - pe_comp.pe_print(f"1) Recherche des coSemestres -> {len(cosemestres)} trouvés") + pe_affichage.pe_print(f"1) Recherche des coSemestres -> {len(cosemestres)} trouvés") - pe_comp.pe_print("2) Liste des étudiants dans les différents co-semestres") + pe_affichage.pe_print("2) Liste des étudiants dans les différents co-semestres") self.etudiants_ids = get_etudiants_dans_semestres(cosemestres) - pe_comp.pe_print( + pe_affichage.pe_print( " => %d étudiants trouvés dans les cosemestres" % len(self.etudiants_ids) ) # Analyse des parcours étudiants pour déterminer leur année effective de diplome # avec prise en compte des redoublements, des abandons, .... - pe_comp.pe_print("3) Analyse des parcours individuels des étudiants") + pe_affichage.pe_print("3) Analyse des parcours individuels des étudiants") no_etud = 0 for no_etud, etudid in enumerate(self.etudiants_ids): @@ -110,10 +109,6 @@ class EtudiantsJuryPE: # Analyse son parcours pour atteindre chaque semestre de la formation self.structure_cursus_etudiant(etudid) - if (no_etud + 1) % 10 == 0: - pe_comp.pe_print(f"{no_etud + 1}") - no_etud += 1 - pe_comp.pe_print() # Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris self.etudiants_diplomes = self.get_etudiants_diplomes() @@ -126,23 +121,23 @@ class EtudiantsJuryPE: """Les formsemestres (des étudiants) dont il faut calculer les moyennes""" # Synthèse - pe_comp.pe_print( + pe_affichage.pe_print( f" => {len(self.etudiants_diplomes)} étudiants à diplômer en {self.annee_diplome}" ) nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_diplomes) - pe_comp.pe_print(f" => {nbre_abandons} étudiants éliminer pour abandon") - pe_comp.pe_print( + pe_affichage.pe_print(f" => {nbre_abandons} étudiants non considérés (redoublement, réorientation, abandon") + pe_affichage.pe_print( f" => {len(self.formsemestres_jury_ids)} semestres dont il faut calculer la moyenne" ) - pe_comp.pe_print( - " => quelques étudiants futurs diplômés : " - + ", ".join([str(etudid) for etudid in list(self.etudiants_diplomes)[:10]]) - ) - pe_comp.pe_print( - " => semestres dont il faut calculer les moyennes : " - + ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)]) - ) - # Les abandons : + # pe_affichage.pe_print( + # " => quelques étudiants futurs diplômés : " + # + ", ".join([str(etudid) for etudid in list(self.etudiants_diplomes)[:10]]) + # ) + # pe_affichage.pe_print( + # " => semestres dont il faut calculer les moyennes : " + # + ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)]) + # ) + # Les abandons (pour debug) self.abandons = sorted( [ cursus["nom"] @@ -416,7 +411,7 @@ def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set: for sem in semestres.values(): # pour chacun des semestres de la liste etudiants_du_sem = {ins.etudid for ins in sem.inscriptions} - pe_print(f" --> {sem} : {len(etudiants_du_sem)} etudiants") + pe_affichage.pe_print(f" --> {sem} : {len(etudiants_du_sem)} etudiants") etudiants_ids = ( etudiants_ids | etudiants_du_sem ) # incluant la suppression des doublons diff --git a/app/pe/pe_interclasstag.py b/app/pe/pe_interclasstag.py index 7f28b3ac..916571a4 100644 --- a/app/pe/pe_interclasstag.py +++ b/app/pe/pe_interclasstag.py @@ -1,8 +1,9 @@ +from app.comp import moy_sem from app.pe.pe_tabletags import TableTag from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE from app.pe.pe_trajectoiretag import TrajectoireTag -from app.comp import moy_sem + import pandas as pd import numpy as np diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index bd46d8c8..6ecb3aff 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -46,29 +46,19 @@ import io import os from zipfile import ZipFile - -from app.comp import res_sem -from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre -from app.models.etudiants import Identite - -from app.scodoc.gen_tables import GenTable, SeqGenTable -import app.scodoc.sco_utils as scu +from app.scodoc.gen_tables import SeqGenTable from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_trajectoire import TrajectoiresJuryPE, Trajectoire import app.pe.pe_comp as pe_comp +import app.pe.pe_affichage as pe_affichage from app.pe.pe_semtag import SemestreTag from app.pe.pe_interclasstag import AggregatInterclasseTag from app.pe.pe_trajectoiretag import TrajectoireTag import app.pe.pe_affichage as pe_affichage import pandas as pd -import numpy as np - -# ---------------------------------------------------------------------------------------- -# ---------------------------------------------------------------------------------------- class JuryPE(object): """Classe mémorisant toutes les informations nécessaires pour établir un jury de PE. Modèle basé sur NotesTable. @@ -112,7 +102,7 @@ class JuryPE(object): self.zipdata = io.BytesIO() with ZipFile(self.zipdata, "w") as zipfile: # Chargement des étudiants à prendre en compte dans le jury - pe_comp.pe_print( + pe_affichage.pe_print( f"""*** Recherche et chargement des étudiants diplômés en { self.diplome} pour la formation {self.formation_id}""" ) @@ -123,42 +113,47 @@ class JuryPE(object): self.diplomes_ids = self.etudiants.diplomes_ids # Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE - pe_comp.pe_print("*** Génère les semestres taggués") + pe_affichage.pe_print("*** Génère les semestres taggués") self.semestres_taggues = compute_semestres_tag(self.etudiants) - if pe_comp.PE_DEBUG: - # Intègre le bilan des semestres taggués au zip final + # Intègre le bilan des semestres taggués au zip final + output = io.BytesIO() + with pd.ExcelWriter(output, engine="openpyxl") as writer: for formsemestretag in self.semestres_taggues.values(): - filename = formsemestretag.nom.replace(" ", "_") + ".csv" - pe_comp.pe_print(f" - Export csv de {filename} ") - self.add_file_to_zip( - zipfile, - filename, - formsemestretag.str_tagtable(), - path="details_semestres", - ) + onglet = formsemestretag.nom + df = formsemestretag.df_semtag() + # écriture dans l'onglet + df.to_excel(writer, onglet, index=True, header=True) + output.seek(0) + + self.add_file_to_zip( + zipfile, + f"semestres_taggues_{self.diplome}.xlsx", + output.read(), + path="details", + ) # Génère les trajectoires (combinaison de semestres suivis # par un étudiant pour atteindre le semestre final d'un aggrégat) - pe_comp.pe_print( + pe_affichage.pe_print( "*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants" ) self.trajectoires = TrajectoiresJuryPE(self.diplome) self.trajectoires.cree_trajectoires(self.etudiants) # Génère les moyennes par tags des trajectoires - pe_comp.pe_print( + pe_affichage.pe_print( "*** Calcule les moyennes par tag des trajectoires possibles" ) self.trajectoires_tagguees = compute_trajectoires_tag( self.trajectoires, self.etudiants, self.semestres_taggues ) - if pe_comp.PE_DEBUG: + if pe_affichage.PE_DEBUG: # Intègre le bilan des trajectoires tagguées au zip final for trajectoire_tagguee in self.trajectoires_tagguees.values(): filename = trajectoire_tagguee.get_repr().replace(" ", "_") + ".csv" - pe_comp.pe_print(f" - Export csv de {filename} ") + # pe_affichage.pe_print(f" - Export csv de {filename} ") self.add_file_to_zip( zipfile, filename, @@ -167,16 +162,16 @@ class JuryPE(object): ) # Génère les interclassements (par promo et) par (nom d') aggrégat - pe_comp.pe_print("*** Génère les interclassements par aggrégat") + pe_affichage.pe_print("*** Génère les interclassements par aggrégat") self.interclassements_taggues = compute_interclassements( self.etudiants, self.trajectoires, self.trajectoires_tagguees ) - if pe_comp.PE_DEBUG: + if pe_affichage.PE_DEBUG: # Intègre le bilan des aggrégats (par promo) au zip final for interclass_tag in self.interclassements_taggues.values(): filename = interclass_tag.get_repr().replace(" ", "_") + ".csv" - pe_comp.pe_print(f" - Export csv de {filename} ") + # pe_affichage.pe_print(f" - Export csv de {filename} ") self.add_file_to_zip( zipfile, filename, @@ -188,7 +183,7 @@ class JuryPE(object): self.synthese = self.synthetise_juryPE() # Export des données => mode 1 seule feuille -> supprimé - pe_comp.pe_print("*** Export du jury de synthese") + pe_affichage.pe_print("*** Export du jury de synthese") output = io.BytesIO() with pd.ExcelWriter(output, engine="openpyxl") as writer: @@ -241,15 +236,15 @@ class JuryPE(object): def synthetise_juryPE(self): """Synthétise tous les résultats du jury PE dans des dataframes""" - pe_comp.pe_print("*** Synthèse finale des moyennes ***") + pe_affichage.pe_print("*** Synthèse finale des moyennes ***") synthese = {} - pe_comp.pe_print(" -> Synthèse des données administratives") + pe_affichage.pe_print(" -> Synthèse des données administratives") synthese["administratif"] = self.df_administratif() tags = self.do_tags_list(self.interclassements_taggues) for tag in tags: - pe_comp.pe_print(f" -> Synthèse du tag {tag}") + pe_affichage.pe_print(f" -> Synthèse du tag {tag}") synthese[tag] = self.df_tag(tag) return synthese @@ -325,7 +320,7 @@ class JuryPE(object): donnees[etudid] |= { f"{aggregat} notes ": "-", f"{aggregat} class. (groupe)": "-", - f"{aggregat} min/moy/max (groupe)": "-", + f"{aggregat} min | moy | max (groupe)": "-", } if trajectoire: trajectoire_tagguee = self.trajectoires_tagguees[ @@ -335,24 +330,23 @@ class JuryPE(object): bilan = trajectoire_tagguee.moyennes_tags[tag] donnees[etudid] |= { - f"{aggregat} notes ": round(bilan['notes'].loc[etudid], 2), + f"{aggregat} notes ": round(bilan["notes"].loc[etudid], 2), f"{aggregat} class. (groupe)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}", - f"{aggregat} min/moy/max (groupe)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}", + f"{aggregat} min | moy | max (groupe)": f"{bilan['min']:.1f} | {bilan['moy']:.1f} | {bilan['max']:.1f}", } - """L'interclassement""" interclass = self.interclassements_taggues[aggregat] donnees[etudid] |= { f"{aggregat} class. (promo)": "-", - f"{aggregat} min/moy/max (promo)": "-", + f"{aggregat} min | moy | max (promo)": "-", } if tag in interclass.moyennes_tags: bilan = interclass.moyennes_tags[tag] donnees[etudid] |= { f"{aggregat} class. (promo)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}", - f"{aggregat} min/moy/max (promo)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}", + f"{aggregat} min | moy | max (promo)": f"{bilan['min']:.1f} | {bilan['moy']:.1f} | {bilan['max']:.1f}", } # Fin de l'aggrégat @@ -390,7 +384,7 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict: """ """Création des semestres taggués, de type 'S1', 'S2', ...""" - pe_comp.pe_print("*** Création des semestres taggués") + pe_affichage.pe_print("*** Création des semestres taggués") formsemestres = etudiants.get_formsemestres( semestres_recherches=pe_comp.TOUS_LES_SEMESTRES @@ -398,19 +392,11 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict: semestres_tags = {} for frmsem_id, formsemestre in formsemestres.items(): - """Choix d'un nom pour le semestretag""" - nom = "S%d %d %d-%d" % ( - formsemestre.semestre_id, - frmsem_id, - formsemestre.date_debut.year, - formsemestre.date_fin.year, - ) - - pe_comp.pe_print(f" --> Semestre taggué {nom} sur la base de {formsemestre}") - # Crée le semestre_tag et exécute les calculs de moyennes - formsemestretag = SemestreTag(nom, frmsem_id) - + formsemestretag = SemestreTag(frmsem_id) + pe_affichage.pe_print( + f" --> Semestre taggué {formsemestretag.nom} sur la base de {formsemestre}" + ) # Stocke le semestre taggué semestres_tags[frmsem_id] = formsemestretag @@ -443,23 +429,16 @@ def compute_trajectoires_tag( semestres_tag: Les semestres tag (pour lesquels des moyennes par tag ont été calculés) Return: - Un dictionnaire de la forme {nom_aggregat: {fid_terminal: SetTag(fid_terminal)} } + Un dictionnaire de la forme ``{nom_aggregat: {fid_terminal: SetTag(fid_terminal)} }`` """ - - pe_comp.pe_print(" *** Création des aggrégats ") - trajectoires_tagguees = {} - for trajectoire_id in trajectoires.trajectoires: - trajectoire = trajectoires.trajectoires[trajectoire_id] + for trajectoire_id, trajectoire in trajectoires.trajectoires.items(): nom = trajectoire.get_repr() - - pe_comp.pe_print(f" --> Fusion {nom}") - + pe_affichage.pe_print(f" --> Aggrégat {nom}") + # Trajectoire_tagguee associée trajectoire_tagguee = TrajectoireTag(nom, trajectoire, semestres_taggues) - """Trajectoire_tagguee associée""" - - """Mémorise le résultat""" + # Mémorise le résultat trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee return trajectoires_tagguees @@ -474,11 +453,9 @@ def compute_interclassements( pour fournir un classement sur la promo. Le classement est établi au regard du nombre d'étudiants ayant participé au même aggrégat. """ - pe_comp.pe_print(" Interclassement sur la promo") - aggregats_interclasses_taggues = {} for nom_aggregat in pe_comp.TOUS_LES_SEMESTRES + pe_comp.TOUS_LES_AGGREGATS: - pe_comp.pe_print(f" --> {nom_aggregat}") + pe_affichage.pe_print(f" --> Interclassement {nom_aggregat}") interclass = AggregatInterclasseTag( nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees ) diff --git a/app/pe/pe_semtag.py b/app/pe/pe_semtag.py index dbf0c02a..b6e8b9d0 100644 --- a/app/pe/pe_semtag.py +++ b/app/pe/pe_semtag.py @@ -45,63 +45,63 @@ from app.models.moduleimpls import ModuleImpl from app.scodoc import sco_tag_module from app.scodoc.codes_cursus import UE_SPORT -import app.pe.pe_comp as pe_comp -from app.pe.pe_tabletags import (TableTag, TAGS_RESERVES) +import app.pe.pe_affichage as pe_affichage +from app.pe.pe_tabletags import TableTag, TAGS_RESERVES +import pandas as pd class SemestreTag(TableTag): - """Un SemestreTag 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. - """ - # ----------------------------------------------------------------------------- - # Fonctions d'initialisation - # ----------------------------------------------------------------------------- - def __init__(self, nom: str, formsemestre_id: int): + def __init__(self, formsemestre_id: int): """ + Un SemestreTag 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. + Args: nom: Nom à donner au SemestreTag - formsemestre_id: Identifiant du FormSemestre sur lequel il se base + formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base """ - TableTag.__init__(self, nom=nom) + # TableTag.__init__(self, nom=nom) - """Le semestre""" + # Le semestre self.formsemestre_id = formsemestre_id self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id) - """Les résultats du semestre""" + # Le nom du semestre taggué + self.nom = self.get_repr() + + # Les résultats du semestre self.nt = load_formsemestre_results(self.formsemestre) - """Les étudiants""" + # Les étudiants self.etuds = self.nt.etuds self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds} - """Les notes, les modules implémentés triés, les étudiants, les coeffs, - récupérés notamment de py:mod:`res_but` - """ + # Les notes, les modules implémentés triés, les étudiants, les coeffs, + # récupérés notamment de py:mod:`res_but` self.sem_cube = self.nt.sem_cube self.modimpls_sorted = self.nt.formsemestre.modimpls_sorted self.modimpl_coefs_df = self.nt.modimpl_coefs_df - """Les inscriptions au module et les dispenses d'UE""" + # Les inscriptions au module et les dispenses d'UE self.modimpl_inscr_df = self.nt.modimpl_inscr_df self.ues = self.nt.ues self.ues_inscr_parcours_df = self.nt.load_ues_inscr_parcours() self.dispense_ues = self.nt.dispense_ues - """Les tags (en supprimant les tags réservés)""" + # Les tags (en supprimant les tags réservés) self.tags = get_synthese_tags_semestre(self.nt.formsemestre) for tag in TAGS_RESERVES: if tag in self.tags: del self.tags[tag] - """Calcul des moyennes & les classements de chaque étudiant à chaque tag""" + # Calcul des moyennes & les classements de chaque étudiant à chaque tag self.moyennes_tags = {} for tag in self.tags: - pe_comp.pe_print(f" -> Traitement du tag {tag}") + # pe_affichage.pe_print(f" -> Traitement du tag {tag}") moy_gen_tag = self.compute_moyenne_tag(tag) - class_gen_tag = moy_sem.comp_ranks_series(moy_gen_tag)[1] # en int + class_gen_tag = moy_sem.comp_ranks_series(moy_gen_tag)[1] # en int self.moyennes_tags[tag] = { "notes": moy_gen_tag, "classements": class_gen_tag, @@ -111,8 +111,7 @@ class SemestreTag(TableTag): "nb_inscrits": len(moy_gen_tag), } - """Ajoute les moyennes générales de BUT pour le semestre considéré""" - pe_comp.pe_print(f" -> Traitement du tag but") + # Ajoute les moyennes générales de BUT pour le semestre considéré moy_gen_but = self.nt.etud_moy_gen class_gen_but = self.nt.etud_moy_gen_ranks_int self.moyennes_tags["but"] = { @@ -124,16 +123,18 @@ class SemestreTag(TableTag): "nb_inscrits": len(moy_gen_but), } - """Synthétise l'ensemble des moyennes dans un dataframe""" - self.tags_sorted = sorted(self.moyennes_tags) # les tags par ordre alphabétique - self.notes = self.df_tagtable() # Le dataframe synthétique des notes (=moyennes par tag) + # Synthétise l'ensemble des moyennes dans un dataframe + self.tags_sorted = sorted(self.moyennes_tags) # les tags par ordre alphabétique + self.notes = ( + self.df_tagtable() + ) # Le dataframe synthétique des notes (=moyennes par tag) - # ----------------------------------------------------------------------------- - def get_etudids(self): - """Renvoie la liste des etud_id des étudiants inscrits au semestre""" - return [etud["etudid"] for etud in self.inscrlist] + pe_affichage.pe_print(f" => Traitement des tags {', '.join(self.tags_sorted)}") + + def get_repr(self): + """Nom affiché pour le semestre taggué""" + return pe_affichage.nom_semestre_etape(self.formsemestre, avec_fid=True) - # ----------------------------------------------------------------------------- def compute_moyenne_tag(self, tag: str) -> list: """Calcule la moyenne des étudiants pour le tag indiqué, pour ce SemestreTag. @@ -192,14 +193,22 @@ class SemestreTag(TableTag): return moy_gen_tag - # ----------------------------------------------------------------------------- def get_noteEtCoeff_modimpl(self, modimpl_id, etudid, profondeur=2): """Renvoie un couple donnant la note et le coeff normalisé d'un étudiant à un module d'id modimpl_id. La note et le coeff sont extraits : 1) soit des données du semestre en normalisant le coefficient par rapport à la somme des coefficients des modules du semestre, 2) soit des données des UE précédemment capitalisées, en recherchant un module de même CODE que le modimpl_id proposé, le coefficient normalisé l'étant alors par rapport au total des coefficients du semestre auquel appartient l'ue capitalisée + + TODO:: A rependre si nécessaire """ + + def get_ue_capitalisees(etudid) -> list[dict]: + """Renvoie la liste des capitalisation effectivement capitalisées par un étudiant""" + if etudid in self.nt.validations.ue_capitalisees.index: + return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records") + return [] + (note, coeff_norm) = (None, None) modimpl = get_moduleimpl(modimpl_id) # Le module considéré @@ -207,7 +216,7 @@ class SemestreTag(TableTag): return (None, None) # Y-a-t-il eu capitalisation d'UE ? - ue_capitalisees = self.get_ue_capitalisees( + ue_capitalisees = get_ue_capitalisees( etudid ) # les ue capitalisées des étudiants ue_capitalisees_id = { @@ -273,130 +282,18 @@ class SemestreTag(TableTag): # Sinon - pas de notes à prendre en compte return (note, coeff_norm) - # ----------------------------------------------------------------------------- - def get_ue_capitalisees(self, etudid) -> list[dict]: - """Renvoie la liste des capitalisation effectivement capitalisées par un étudiant""" - if etudid in self.nt.validations.ue_capitalisees.index: - return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records") - return [] + def df_semtag(self): + """Renvoie un dataframe listant toutes les moyennes, + les rangs des étudiants pour tous les tags dans le semestre taggué""" - # ----------------------------------------------------------------------------- - # Fonctions d'affichage (et d'export csv) des données du semestre en mode debug - # ----------------------------------------------------------------------------- - def str_detail_resultat_d_un_tag(self, tag, etudid=None, delim=";"): - """Renvoie une chaine de caractère décrivant les résultats d'étudiants à un tag : - rappelle les notes obtenues dans les modules à prendre en compte, les moyennes et les rangs calculés. - Si etudid=None, tous les étudiants inscrits dans le semestre sont pris en compte. Sinon seuls les étudiants indiqués sont affichés. - """ - # Entete - chaine = delim.join(["%15s" % "nom", "etudid"]) + delim - taglist = self.get_all_tags() - if tag in taglist: - for mod in self.tagdict[tag].values(): - chaine += mod["module_code"] + delim - chaine += ("%1.1f" % mod["ponderation"]) + delim - chaine += "coeff" + delim - chaine += delim.join( - ["moyenne", "rang", "nbinscrit", "somme_coeff", "somme_coeff"] - ) # ligne 1 - chaine += "\n" + etudiants = self.etudiants + df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"]) - # Différents cas de boucles sur les étudiants (de 1 à plusieurs) - if etudid == None: - lesEtuds = self.get_etudids() - elif isinstance(etudid, str) and etudid in self.get_etudids(): - lesEtuds = [etudid] - elif isinstance(etudid, list): - lesEtuds = [eid for eid in self.get_etudids() if eid in etudid] - else: - lesEtuds = [] - - for etudid in lesEtuds: - descr = ( - "%15s" % self.nt.get_nom_short(etudid)[:15] - + delim - + str(etudid) - + delim - ) - if tag in taglist: - for modimpl_id in self.tagdict[tag]: - (note, coeff) = self.get_noteEtCoeff_modimpl(modimpl_id, etudid) - descr += ( - ( - "%2.2f" % note - if note != None and isinstance(note, float) - else str(note) - ) - + delim - + ( - "%1.5f" % coeff - if coeff != None and isinstance(coeff, float) - else str(coeff) - ) - + delim - + ( - "%1.5f" % (coeff * self.somme_coeffs) - if coeff != None and isinstance(coeff, float) - else "???" # str(coeff * self._sum_coeff_semestre) # voir avec Cléo - ) - + delim - ) - moy = self.get_moy_from_resultats(tag, etudid) - rang = self.get_rang_from_resultats(tag, etudid) - coeff = self.get_coeff_from_resultats(tag, etudid) - tot = ( - coeff * self.somme_coeffs - if coeff != None - and self.somme_coeffs != None - and isinstance(coeff, float) - else None - ) - descr += ( - pe_tagtable.TableTag.str_moytag( - moy, rang, len(self.get_etudids()), delim=delim - ) - + delim - ) - descr += ( - ( - "%1.5f" % coeff - if coeff != None and isinstance(coeff, float) - else str(coeff) - ) - + delim - + ( - "%.2f" % (tot) - if tot != None - else str(coeff) + "*" + str(self.somme_coeffs) - ) - ) - chaine += descr - chaine += "\n" - return chaine - - def str_tagsModulesEtCoeffs(self): - """Renvoie une chaine affichant la liste des tags associés au semestre, - les modules qui les concernent et les coeffs de pondération. - Plus concrètement permet d'afficher le contenu de self._tagdict""" - chaine = "Semestre %s d'id %d" % (self.nom, id(self)) + "\n" - chaine += " -> somme de coeffs: " + str(self.somme_coeffs) + "\n" - taglist = self.get_all_tags() - for tag in taglist: - chaine += " > " + tag + ": " - for modid, mod in self.tagdict[tag].items(): - chaine += ( - mod["module_code"] - + " (" - + str(mod["coeff"]) - + "*" - + str(mod["ponderation"]) - + ") " - + str(modid) - + ", " - ) - chaine += "\n" - return chaine + for tag in self.get_all_tags(): + df = df.join(self.moyennes_tags[tag]["notes"].rename(f"Moy {tag}")) + df = df.join(self.moyennes_tags[tag]["classements"].rename(f"Class {tag}")) + return df # ************************************************************************ # Fonctions diverses @@ -437,8 +334,8 @@ def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float: def get_synthese_tags_semestre(formsemestre: FormSemestre): """Etant données les implémentations des modules du semestre (modimpls), synthétise les tags les concernant (tags saisis dans le programme pédagogique) - en les associant aux modimpls qui les concernent (modimpl_id, module_id, - le code du module, coeff et pondération fournie avec le tag (par défaut 1 si non indiquée)). + en les associant aux modimpls qui les concernent (modimpl_id) et + aucoeff et pondération fournie avec le tag (par défaut 1 si non indiquée)). { tagname1: { modimpl_id1: { 'module_id': ..., 'coeff': ..., @@ -451,6 +348,9 @@ def get_synthese_tags_semestre(formsemestre: FormSemestre): Args: formsemestre: Le formsemestre à la base de la recherche des tags + + Return: + Un dictionnaire de tags """ synthese_tags = {} diff --git a/app/pe/pe_tabletags.py b/app/pe/pe_tabletags.py index 5c995095..f687cd5c 100644 --- a/app/pe/pe_tabletags.py +++ b/app/pe/pe_tabletags.py @@ -91,188 +91,6 @@ class TableTag(object): return sorted(self.moyennes_tags.keys()) - # ***************************************************************************************************************** - # Accesseurs - # ***************************************************************************************************************** - - # ----------------------------------------------------------------------------------------------------------- - def get_moy_from_resultats(self, tag, etudid): - """Renvoie la moyenne obtenue par un étudiant à un tag donné au regard du format de self.resultats""" - return ( - self.moyennes_tags[tag][etudid][0] - if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag] - else None - ) - - # ----------------------------------------------------------------------------------------------------------- - def get_rang_from_resultats(self, tag, etudid): - """Renvoie le rang à un tag d'un étudiant au regard du format de self.resultats""" - return ( - self.rangs[tag][etudid] - if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag] - else None - ) - - # ----------------------------------------------------------------------------------------------------------- - def get_coeff_from_resultats(self, tag, etudid): - """Renvoie la somme des coeffs de pondération normalisée utilisés dans le calcul de la moyenne à un tag d'un étudiant - au regard du format de self.resultats. - """ - return ( - self.moyennes_tags[tag][etudid][1] - if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag] - else None - ) - - - # ----------------------------------------------------------------------------------------------------------- - def get_nbinscrits(self): - """Renvoie le nombre d'inscrits""" - return len(self.inscrlist) - - # ----------------------------------------------------------------------------------------------------------- - def get_moy_from_stats(self, tag): - """Renvoie la moyenne des notes calculées pour d'un tag donné""" - return self.statistiques[tag][0] if tag in self.statistiques else None - - def get_min_from_stats(self, tag): - """Renvoie la plus basse des notes calculées pour d'un tag donné""" - return self.statistiques[tag][1] if tag in self.statistiques else None - - def get_max_from_stats(self, tag): - """Renvoie la plus haute des notes calculées pour d'un tag donné""" - return self.statistiques[tag][2] if tag in self.statistiques else None - - # ----------------------------------------------------------------------------------------------------------- - # La structure des données mémorisées pour chaque tag dans le dictionnaire de synthèse - # d'un jury PE - FORMAT_DONNEES_ETUDIANTS = ( - "note", - "coeff", - "rang", - "nbinscrits", - "moy", - "max", - "min", - ) - - def get_resultatsEtud(self, tag, etudid): - """Renvoie un tuple (note, coeff, rang, nb_inscrit, moy, min, max) synthétisant les résultats d'un étudiant - à un tag donné. None sinon""" - return ( - self.get_moy_from_resultats(tag, etudid), - self.get_coeff_from_resultats(tag, etudid), - self.get_rang_from_resultats(tag, etudid), - self.get_nbinscrits(), - self.get_moy_from_stats(tag), - self.get_min_from_stats(tag), - self.get_max_from_stats(tag), - ) - - # return self.tag_stats[tag] - # else : - # return self.pe_stats - - # ***************************************************************************************************************** - # Ajout des notes - # ***************************************************************************************************************** - - # ----------------------------------------------------------------------------------------------------------- - def add_moyennesTag(self, tag, listMoyEtCoeff) -> bool: - """ - Mémorise les moyennes, les coeffs de pondération et les etudid dans resultats - avec calcul du rang - :param tag: Un tag - :param listMoyEtCoeff: Une liste donnant [ (moy, coeff, etudid) ] - - TODO:: Inutile maintenant ? - """ - # ajout des moyennes au dictionnaire résultat - if listMoyEtCoeff: - self.moyennes_tags[tag] = { - etudid: (moyenne, somme_coeffs) - for (moyenne, somme_coeffs, etudid) in listMoyEtCoeff - } - - # Calcule les rangs - lesMoyennesTriees = sorted( - listMoyEtCoeff, - reverse=True, - key=lambda col: col[0] - if isinstance(col[0], float) - else 0, # remplace les None et autres chaines par des zéros - ) # triées - self.rangs[tag] = scu.comp_ranks(lesMoyennesTriees) # les rangs - - # calcul des stats - self.comp_stats_d_un_tag(tag) - return True - return False - - # ***************************************************************************************************************** - # Méthodes dévolues aux calculs de statistiques (min, max, moy) sur chaque moyenne taguée - # ***************************************************************************************************************** - - def comp_stats_d_un_tag(self, tag): - """ - Calcule la moyenne generale, le min, le max pour un tag donné, - en ne prenant en compte que les moyennes significatives. Mémorise le resultat dans - self.statistiques - """ - stats = ("-NA-", "-", "-") - if tag not in self.moyennes_tags: - return stats - - notes = [ - self.get_moy_from_resultats(tag, etudid) - for etudid in self.moyennes_tags[tag] - ] # les notes du tag - notes_valides = [ - note for note in notes if isinstance(note, float) and note != None - ] - nb_notes_valides = len(notes_valides) - if nb_notes_valides > 0: - (moy, _) = moyenne_ponderee_terme_a_terme(notes_valides, force=True) - self.statistiques[tag] = (moy, max(notes_valides), min(notes_valides)) - - # ************************************************************************ - # Méthodes dévolues aux affichages -> a revoir - # ************************************************************************ - def str_resTag_d_un_etudiant(self, tag, etudid, delim=";"): - """Renvoie une chaine de caractères (valable pour un csv) - décrivant la moyenne et le rang d'un étudiant, pour un tag donné ; - """ - if tag not in self.get_all_tags() or etudid not in self.moyennes_tags[tag]: - return "" - - moystr = TableTag.str_moytag( - self.get_moy_from_resultats(tag, etudid), - self.get_rang_from_resultats(tag, etudid), - self.get_nbinscrits(), - delim=delim, - ) - return moystr - - def str_res_d_un_etudiant(self, etudid, delim=";"): - """Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique).""" - return delim.join( - [self.str_resTag_d_un_etudiant(tag, etudid) for tag in self.get_all_tags()] - ) - - # ----------------------------------------------------------------------- - def str_moytag(cls, moyenne, rang, nbinscrit, delim=";"): - """Renvoie une chaine de caractères représentant une moyenne (float ou string) et un rang - pour différents formats d'affichage : HTML, debug ligne de commande, csv""" - moystr = ( - "%2.2f%s%s%s%d" % (moyenne, delim, rang, delim, nbinscrit) - if isinstance(moyenne, float) - else str(moyenne) + delim + str(rang) + delim + str(nbinscrit) - ) - return moystr - - str_moytag = classmethod(str_moytag) - # ----------------------------------------------------------------------- - def df_tagtable(self): """Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags @@ -300,68 +118,3 @@ class TableTag(object): return df.to_csv(sep=";") - -# ************************************************************************ -# Fonctions diverses -# ************************************************************************ - - -# ********************************************* -def moyenne_ponderee_terme_a_terme(notes, coefs=None, force=False): - """ - Calcule la moyenne pondérée d'une liste de notes avec d'éventuels coeffs de pondération. - Renvoie le résultat sous forme d'un tuple (moy, somme_coeff) - - La liste de notes contient soit : - 1) des valeurs numériques - 2) des strings "-NA-" (pas de notes) ou "-NI-" (pas inscrit) ou "-c-" ue capitalisée, - 3) None. - - Le paramètre force indique si le calcul de la moyenne doit être forcée ou non, c'est à - dire s'il y a ou non omission des notes non numériques (auquel cas la moyenne est - calculée sur les notes disponibles) ; sinon renvoie (None, None). - """ - # Vérification des paramètres d'entrée - if not isinstance(notes, list) or ( - coefs != None and not isinstance(coefs, list) and len(coefs) != len(notes) - ): - raise ValueError("Erreur de paramètres dans moyenne_ponderee_terme_a_terme") - - # Récupération des valeurs des paramètres d'entrée - coefs = [1] * len(notes) if coefs is None else coefs - - # S'il n'y a pas de notes - if not notes: # Si notes = [] - return (None, None) - - # Liste indiquant les notes valides - notes_valides = [ - (isinstance(note, float) and not np.isnan(note)) or isinstance(note, int) - for note in notes - ] - # Si on force le calcul de la moyenne ou qu'on ne le force pas - # et qu'on a le bon nombre de notes - if force or sum(notes_valides) == len(notes): - moyenne, ponderation = 0.0, 0.0 - for i in range(len(notes)): - if notes_valides[i]: - moyenne += coefs[i] * notes[i] - ponderation += coefs[i] - return ( - (moyenne / (ponderation * 1.0), ponderation) - if ponderation != 0 - else (None, 0) - ) - # Si on ne force pas le calcul de la moyenne - return (None, None) - - -# ------------------------------------------------------------------------------------------- -def conversionDate_StrToDate(date_fin): - """Conversion d'une date fournie sous la forme d'une chaine de caractère de - type 'jj/mm/aaaa' en un objet date du package datetime. - Fonction servant au tri des semestres par date - """ - (d, m, y) = [int(x) for x in date_fin.split("/")] - date_fin_dst = datetime.date(y, m, d) - return date_fin_dst diff --git a/app/pe/pe_trajectoire.py b/app/pe/pe_trajectoire.py index 01b32090..5c92593e 100644 --- a/app/pe/pe_trajectoire.py +++ b/app/pe/pe_trajectoire.py @@ -1,4 +1,6 @@ import app.pe.pe_comp as pe_comp +import app.pe.pe_affichage as pe_affichage + from app.models import FormSemestre from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date @@ -129,7 +131,7 @@ def get_trajectoires_etudid(trajectoires, etudid): trajectoires suivies par un étudiant """ if etudid not in trajectoires.suivi: - pe_comp.pe_print(f"{etudid} fait-il bien partie du jury ?") + pe_affichage.pe_print(f"{etudid} fait-il bien partie du jury ?") liste = [] for aggregat in pe_comp.TOUS_LES_PARCOURS: diff --git a/app/pe/pe_trajectoiretag.py b/app/pe/pe_trajectoiretag.py index 07b742ca..9f75fdfd 100644 --- a/app/pe/pe_trajectoiretag.py +++ b/app/pe/pe_trajectoiretag.py @@ -39,12 +39,10 @@ Created on Fri Sep 9 09:15:05 2016 from app.comp import moy_sem from app.comp.res_sem import load_formsemestre_results from app.pe.pe_semtag import SemestreTag -from app.pe import pe_tabletags import pandas as pd import numpy as np from app.pe.pe_trajectoire import Trajectoire -from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_tabletags import TableTag