diff --git a/app/pe/pe_comp.py b/app/pe/pe_comp.py index f65057fe4..8a2a8171a 100644 --- a/app/pe/pe_comp.py +++ b/app/pe/pe_comp.py @@ -372,3 +372,5 @@ def get_cosemestres_diplomants( cosemestres[fid] = cosem return cosemestres + + diff --git a/app/pe/pe_interclasstag.py b/app/pe/pe_interclasstag.py index 916571a4f..ffb2a03a4 100644 --- a/app/pe/pe_interclasstag.py +++ b/app/pe/pe_interclasstag.py @@ -10,14 +10,6 @@ import numpy as np class AggregatInterclasseTag(TableTag): - """Interclasse l'ensemble des étudiants diplômés à une année - donnée (celle du jury), pour un aggrégat donné (par ex: 'S2', '3S') - en reportant : - - * les moyennes obtenues sur la trajectoire qu'il ont suivi pour atteindre le numéro de semestre de fin de l'aggrégat (indépendamment de son - formsemestres) - * calculant le classement sur les étudiants diplômes - """ # ------------------------------------------------------------------------------------------------------------------- def __init__( @@ -27,12 +19,26 @@ class AggregatInterclasseTag(TableTag): trajectoires_jury_pe: TrajectoiresJuryPE, trajectoires_taggues: dict[tuple, TrajectoireTag], ): - # Table nommée au nom de l'aggrégat (par ex: 3S) - TableTag.__init__(self, nom_aggregat) + """ + Interclasse l'ensemble des étudiants diplômés à une année + donnée (celle du jury), pour un aggrégat donné (par ex: 'S2', '3S') + en reportant : + + * les moyennes obtenues sur la trajectoire qu'il ont suivi pour atteindre le numéro de semestre de fin de l'aggrégat (indépendamment de son + formsemestres) + * calculant le classement sur les étudiants diplômes + """ + TableTag.__init__(self) + + # Le nom + self.aggregat = nom_aggregat + self.nom = self.get_repr() """Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)""" # TODO self.diplomes_ids = etudiants.etudiants_diplomes self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids} + # pour les exports sous forme de dataFrame + self.etudiants = {etudid: etudiants.identites[etudid].etat_civil for etudid in self.diplomes_ids} # Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat self.trajectoires: dict[int, Trajectoire] = {} @@ -73,9 +79,13 @@ class AggregatInterclasseTag(TableTag): "nb_inscrits": len(moy_gen_tag), } + # Est significatif ? (aka a-t-il des tags et des notes) + self.significatif = len(self.tags_sorted) > 0 + + def get_repr(self) -> str: """Une représentation textuelle""" - return f"Aggrégat {self.nom}" + return f"Aggrégat {self.aggregat}" def do_taglist(self): """Synthétise les tags à partir des trajectoires_tagguées diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index 6ecb3affe..645d0b1dc 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -121,7 +121,7 @@ class JuryPE(object): with pd.ExcelWriter(output, engine="openpyxl") as writer: for formsemestretag in self.semestres_taggues.values(): onglet = formsemestretag.nom - df = formsemestretag.df_semtag() + df = formsemestretag.df_moyennes_et_classements() # écriture dans l'onglet df.to_excel(writer, onglet, index=True, header=True) output.seek(0) @@ -149,17 +149,22 @@ class JuryPE(object): self.trajectoires, self.etudiants, self.semestres_taggues ) - if pe_affichage.PE_DEBUG: - # Intègre le bilan des trajectoires tagguées au zip final + # Intègre le bilan des trajectoires tagguées au zip final + output = io.BytesIO() + with pd.ExcelWriter(output, engine="openpyxl") as writer: for trajectoire_tagguee in self.trajectoires_tagguees.values(): - filename = trajectoire_tagguee.get_repr().replace(" ", "_") + ".csv" - # pe_affichage.pe_print(f" - Export csv de {filename} ") - self.add_file_to_zip( - zipfile, - filename, - trajectoire_tagguee.str_tagtable(), - path="details_semestres", - ) + onglet = trajectoire_tagguee.get_repr() + df = trajectoire_tagguee.df_moyennes_et_classements() + # écriture dans l'onglet + df.to_excel(writer, onglet, index=True, header=True) + output.seek(0) + + self.add_file_to_zip( + zipfile, + f"trajectoires_taggues_{self.diplome}.xlsx", + output.read(), + path="details", + ) # Génère les interclassements (par promo et) par (nom d') aggrégat pe_affichage.pe_print("*** Génère les interclassements par aggrégat") @@ -167,17 +172,23 @@ class JuryPE(object): self.etudiants, self.trajectoires, self.trajectoires_tagguees ) - if pe_affichage.PE_DEBUG: - # Intègre le bilan des aggrégats (par promo) au zip final + # Intègre le bilan des aggrégats (interclassé par promo) au zip final + output = io.BytesIO() + with pd.ExcelWriter(output, engine="openpyxl") as writer: for interclass_tag in self.interclassements_taggues.values(): - filename = interclass_tag.get_repr().replace(" ", "_") + ".csv" - # pe_affichage.pe_print(f" - Export csv de {filename} ") - self.add_file_to_zip( - zipfile, - filename, - interclass_tag.str_tagtable(), - path="details_semestres", - ) + if interclass_tag.significatif: # Avec des notes + onglet = interclass_tag.get_repr() + df = interclass_tag.df_moyennes_et_classements() + # écriture dans l'onglet + df.to_excel(writer, onglet, index=True, header=True) + output.seek(0) + + self.add_file_to_zip( + zipfile, + f"interclassements_taggues_{self.diplome}.xlsx", + output.read(), + path="details", + ) # Synthèse des éléments du jury PE self.synthese = self.synthetise_juryPE() @@ -437,7 +448,7 @@ def compute_trajectoires_tag( nom = trajectoire.get_repr() pe_affichage.pe_print(f" --> Aggrégat {nom}") # Trajectoire_tagguee associée - trajectoire_tagguee = TrajectoireTag(nom, trajectoire, semestres_taggues) + trajectoire_tagguee = TrajectoireTag(trajectoire, semestres_taggues) # Mémorise le résultat trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee diff --git a/app/pe/pe_semtag.py b/app/pe/pe_semtag.py index b6e8b9d08..d77f1caf8 100644 --- a/app/pe/pe_semtag.py +++ b/app/pe/pe_semtag.py @@ -61,7 +61,7 @@ class SemestreTag(TableTag): nom: Nom à donner au SemestreTag formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base """ - # TableTag.__init__(self, nom=nom) + TableTag.__init__(self) # Le semestre self.formsemestre_id = formsemestre_id @@ -126,7 +126,7 @@ class SemestreTag(TableTag): # 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() + self.df_notes() ) # Le dataframe synthétique des notes (=moyennes par tag) pe_affichage.pe_print(f" => Traitement des tags {', '.join(self.tags_sorted)}") @@ -282,28 +282,7 @@ class SemestreTag(TableTag): # Sinon - pas de notes à prendre en compte return (note, coeff_norm) - def df_semtag(self): - """Renvoie un dataframe listant toutes les moyennes, - les rangs des étudiants pour tous les tags dans le semestre taggué""" - etudiants = self.etudiants - df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"]) - - 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 -# ************************************************************************ - - -# ********************************************* - - -# ----------------------------------------------------------------------------- def get_moduleimpl(modimpl_id) -> dict: """Renvoie l'objet modimpl dont l'id est modimpl_id""" modimpl = db.session.get(ModuleImpl, modimpl_id) @@ -317,7 +296,6 @@ def get_moduleimpl(modimpl_id) -> dict: return None -# ********************************************** def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float: """Renvoie la moyenne de l'UE d'un etudid dans laquelle se trouve le module de modimpl_id diff --git a/app/pe/pe_tabletags.py b/app/pe/pe_tabletags.py index f687cd5c5..ae4a7fe45 100644 --- a/app/pe/pe_tabletags.py +++ b/app/pe/pe_tabletags.py @@ -48,38 +48,13 @@ TAGS_RESERVES = ["but"] class TableTag(object): - """ - Classe mémorisant les moyennes des étudiants à différents tags et permettant de - calculer des rangs et des statistiques. - Ses attributs sont: - * nom : Nom représentatif des données de la Table - * inscrlist : Les étudiants inscrits dans le TagTag avec leur information de la forme : - { etudid : dictionnaire d'info extrait de Scodoc, ...} - * taglist : Liste triée des noms des tags - * resultats : Dictionnaire donnant les notes-moyennes de chaque étudiant par tag et la somme commulée - des coeff utilisées dans le calcul de la moyenne pondérée, sous la forme : - { tag : { etudid: (note_moy, somme_coeff_norm), - ...} - * rangs : Dictionnaire donnant les rang par tag de chaque étudiant de la forme : - { tag : {etudid: rang, ...} } - * nbinscrits : Nombre d'inscrits dans le semestre (pas de distinction entre les tags) - * statistiques : Dictionnaire donnant les statistiques (moyenne, min, max) des résultats par tag de la forme : - { tag : (moy, min, max), ...} - - """ - - def __init__(self, nom: str): - """Les attributs basiques des TagTable, qui seront initialisés - dans les classes dérivées + def __init__(self): + """Classe centralisant différentes méthodes communes aux + SemestreTag, TrajectoireTag, AggregatInterclassTag """ - self.nom = nom - """Les étudiants""" - self.etudiants = {} - """Les moyennes par tag""" - self.moyennes_tags = {} - + pass # ----------------------------------------------------------------------------------------------------------- def get_all_tags(self): @@ -90,8 +65,21 @@ class TableTag(object): """ return sorted(self.moyennes_tags.keys()) + def df_moyennes_et_classements(self): + """Renvoie un dataframe listant toutes les moyennes, + et les classements des étudiants pour tous les tags + """ - def df_tagtable(self): + etudiants = self.etudiants + df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"]) + + 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 + + def df_notes(self): """Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags Returns: @@ -117,4 +105,3 @@ class TableTag(object): df = df.join(self.moyennes_tags[tag]["classements"].rename(f"class {tag}")) return df.to_csv(sep=";") - diff --git a/app/pe/pe_trajectoire.py b/app/pe/pe_trajectoire.py index 5c92593e4..fd98d031b 100644 --- a/app/pe/pe_trajectoire.py +++ b/app/pe/pe_trajectoire.py @@ -46,16 +46,17 @@ class Trajectoire: """ self.semestres_aggreges = self.semestres_aggreges | semestres - def get_repr(self): + def get_repr(self, verbose=True) -> str: """Représentation textuelle d'une trajectoire basée sur ses semestres aggrégés""" + noms = [] for fid in self.semestres_aggreges: semestre = self.semestres_aggreges[fid] noms.append(f"S{semestre.semestre_id}({fid})") noms = sorted(noms) repr = f"{self.nom} ({self.semestre_final.formsemestre_id}) {self.semestre_final.date_fin.year}" - if noms: + if verbose and noms: repr += " - " + "+".join(noms) return repr diff --git a/app/pe/pe_trajectoiretag.py b/app/pe/pe_trajectoiretag.py index 9f75fdfd4..e6f83cd46 100644 --- a/app/pe/pe_trajectoiretag.py +++ b/app/pe/pe_trajectoiretag.py @@ -47,30 +47,30 @@ from app.pe.pe_tabletags import TableTag class TrajectoireTag(TableTag): - """Calcule les moyennes par tag d'une combinaison de semestres - (trajectoires), identifiée par un nom d'aggrégat (par ex: '3S') et - par un semestre terminal, pour extraire les classements par tag pour un - groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous - participé au semestre terminal. - Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S' - - """ - - # ------------------------------------------------------------------------------------------------------------------- def __init__( self, - nom: str, trajectoire: Trajectoire, semestres_taggues: dict[int, SemestreTag] ): - """ """ - TableTag.__init__(self, nom=nom) + """Calcule les moyennes par tag d'une combinaison de semestres + (trajectoires), identifiée par un nom d'aggrégat (par ex: '3S') et + par un semestre terminal, pour extraire les classements par tag pour un + groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous + participé au semestre terminal. - """La trajectoire associée""" + Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S' + + """ + TableTag.__init__(self) + + # La trajectoire associée self.trajectoire_id = trajectoire.trajectoire_id self.trajectoire = trajectoire + # Le nom de la trajectoire tagguée (identique à la trajectoire) + self.nom = self.get_repr() + """Le formsemestre terminal et les semestres aggrégés""" self.formsemestre_terminal = trajectoire.semestre_final nt = load_formsemestre_results(self.formsemestre_terminal) @@ -114,10 +114,10 @@ class TrajectoireTag(TableTag): "nb_inscrits": len(moy_gen_tag), } - def get_repr(self): + def get_repr(self, verbose=False) -> str: """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle est basée)""" - return self.trajectoire.get_repr() + return self.trajectoire.get_repr(verbose=verbose) def compute_notes_cube(self): """Construit le cube de notes (etudid x tags x semestre_aggregé) @@ -159,8 +159,6 @@ class TrajectoireTag(TableTag): return etudids_x_tags_x_semestres - - def do_taglist(self): """Synthétise les tags à partir des semestres (taggués) aggrégés @@ -173,6 +171,8 @@ class TrajectoireTag(TableTag): return sorted(set(tags)) + + def compute_tag_moy(set_cube: np.array, etudids: list, tags: list): """Calcul de la moyenne par tag sur plusieurs semestres. La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles