diff --git a/app/pe/pe_affichage.py b/app/pe/pe_affichage.py index e66934f4..48c1e33b 100644 --- a/app/pe/pe_affichage.py +++ b/app/pe/pe_affichage.py @@ -1,5 +1,3 @@ -from app.models import Formation, FormSemestre -from app.scodoc import codes_cursus from app import log PE_DEBUG = 0 @@ -9,71 +7,13 @@ if not PE_DEBUG: 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: - """Nom d'un semestre à afficher dans le descriptif des étapes de la scolarité - d'un étudiant. - - Par ex: Pour un S2, affiche ``"Semestre 2 FI S014-2015 (129)"`` avec : - - * 2 le numéro du semestre, - * FI la modalité, - * 2014-2015 les dates - - Args: - semestre: Un ``FormSemestre`` - avec_fid: Ajoute le n° du semestre à la description - - Returns: - La chaine de caractères décrivant succintement le semestre - """ - formation: Formation = semestre.formation - parcours = codes_cursus.get_cursus_from_code(formation.type_parcours) - - description = [ - parcours.SESSION_NAME.capitalize(), - str(semestre.semestre_id), - semestre.modalite, # eg FI ou FC - f"{semestre.date_debut.year}-{semestre.date_fin.year}", - ] - if avec_fid: - description.append(f"({semestre.formsemestre_id})") - - return " ".join(description) +# Affichage dans le tableur pe en cas d'absence de notes +SANS_NOTE = "-" +NOM_STAT_GROUPE = "statistiques du groupe" +NOM_STAT_PROMO = "statistiques de la promo" -def etapes_du_cursus(semestres: dict[int, FormSemestre], nbre_etapes_max: int) -> list[str]: - """Partant d'un dictionnaire de semestres (qui retrace - la scolarité d'un étudiant), liste les noms des - semestres (en version abbrégée) - qu'un étudiant a suivi au cours de sa scolarité à l'IUT. - Les noms des semestres sont renvoyés dans un dictionnaire - ``{"etape i": nom_semestre_a_etape_i}`` - avec i variant jusqu'à nbre_semestres_max. (S'il n'y a pas de semestre à l'étape i, - le nom affiché est vide. - - La fonction suppose la liste des semestres triées par ordre - décroissant de date. - - Args: - semestres: une liste de ``FormSemestre`` - nbre_etapes_max: le nombre d'étapes max prise en compte - - Returns: - Une liste de nom de semestre (dans le même ordre que les ``semestres``) - - See also: - app.pe.pe_affichage.nom_semestre_etape - """ - assert len(semestres) <= nbre_etapes_max - - noms = [nom_semestre_etape(sem, avec_fid=False) for (fid, sem) in semestres.items()] - noms = noms[::-1] # trie par ordre croissant - - dico = {f"Etape {i+1}": "" for i in range(nbre_etapes_max)} - for (i, nom) in enumerate(noms): # Charge les noms de semestres - dico[f"Etape {i+1}"] = nom - return dico diff --git a/app/pe/pe_comp.py b/app/pe/pe_comp.py index 8a2a8171..8b1f8985 100644 --- a/app/pe/pe_comp.py +++ b/app/pe/pe_comp.py @@ -75,81 +75,55 @@ TODO:: A améliorer si BUT en moins de 6 semestres PARCOURS = { "S1": { "aggregat": ["S1"], - "ordre": 1, - "affichage_court": "S1", - "affichage_long": "Semestre 1", + "descr": "Semestre 1 (S1)", }, "S2": { "aggregat": ["S2"], - "ordre": 2, - "affichage_court": "S2", - "affichage_long": "Semestre 2", + "descr": "Semestre 2 (S2)", }, "1A": { "aggregat": ["S1", "S2"], - "ordre": 3, - "affichage_court": "1A", - "affichage_long": "1ère année", + "descr": "BUT1 (S1+S2)", }, "S3": { "aggregat": ["S3"], - "ordre": 4, - "affichage_court": "S3", - "affichage_long": "Semestre 3", + "descr": "Semestre 3 (S3)", }, "S4": { "aggregat": ["S4"], - "ordre": 5, - "affichage_court": "S4", - "affichage_long": "Semestre 4", + "descr": "Semestre 4 (S4)", }, "2A": { "aggregat": ["S3", "S4"], - "ordre": 6, - "affichage_court": "2A", - "affichage_long": "2ème année", + "descr": "BUT2 (S3+S4)", }, "3S": { "aggregat": ["S1", "S2", "S3"], - "ordre": 7, - "affichage_court": "S1+S2+S3", - "affichage_long": "BUT du semestre 1 au semestre 3", + "descr": "Moyenne du semestre 1 au semestre 3 (S1+S2+S3)", }, "4S": { "aggregat": ["S1", "S2", "S3", "S4"], - "ordre": 8, - "affichage_court": "BUT", - "affichage_long": "BUT du semestre 1 au semestre 4", + "descr": "Moyenne du semestre 1 au semestre 4 (S1+S2+S3+S4)", }, "S5": { "aggregat": ["S5"], - "ordre": 9, - "affichage_court": "S5", - "affichage_long": "Semestre 5", + "descr": "Semestre 5 (S5)", }, "S6": { "aggregat": ["S6"], - "ordre": 10, - "affichage_court": "S6", - "affichage_long": "Semestre 6", + "descr": "Semestre 6 (S6)", }, "3A": { "aggregat": ["S5", "S6"], - "ordre": 11, - "affichage_court": "3A", - "affichage_long": "3ème année", + "descr": "3ème année (S5+S6)", }, "5S": { "aggregat": ["S1", "S2", "S3", "S4", "S5"], - "ordre": 12, - "affichage_court": "S1+S2+S3+S4+S5", - "affichage_long": "BUT du semestre 1 au semestre 5", + "descr": "Moyenne du semestre 1 au semestre 5 (S1+S2+S3+S4+S5)", }, "6S": { "aggregat": ["S1", "S2", "S3", "S4", "S5", "S6"], - "ordre": 13, - "affichage_court": "BUT", - "affichage_long": "BUT (tout semestre inclus)", + "descr": "Moyenne globale BUT (S1+S2+S3+S4+S5+S6)", }, } NBRE_SEMESTRES_DIPLOMANT = 6 diff --git a/app/pe/pe_etudiant.py b/app/pe/pe_etudiant.py index 811180df..c459752f 100644 --- a/app/pe/pe_etudiant.py +++ b/app/pe/pe_etudiant.py @@ -37,8 +37,9 @@ Created on 17/01/2024 """ import pandas as pd -from app.models import FormSemestre, Identite +from app.models import FormSemestre, Identite, Formation from app.pe import pe_comp, pe_affichage +from app.scodoc import codes_cursus class EtudiantsJuryPE: @@ -467,7 +468,7 @@ class EtudiantsJuryPE: } # Ajout des noms de semestres parcourus - etapes = pe_affichage.etapes_du_cursus(formsemestres, nbre_semestres_max) + etapes = etapes_du_cursus(formsemestres, nbre_semestres_max) administratif[etudid] |= etapes # Construction du dataframe @@ -647,3 +648,71 @@ def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSeme dernier_semestre = semestres[fid] return dernier_semestre return None + + +def etapes_du_cursus( + semestres: dict[int, FormSemestre], nbre_etapes_max: int +) -> list[str]: + """Partant d'un dictionnaire de semestres (qui retrace + la scolarité d'un étudiant), liste les noms des + semestres (en version abbrégée) + qu'un étudiant a suivi au cours de sa scolarité à l'IUT. + Les noms des semestres sont renvoyés dans un dictionnaire + ``{"etape i": nom_semestre_a_etape_i}`` + avec i variant jusqu'à nbre_semestres_max. (S'il n'y a pas de semestre à l'étape i, + le nom affiché est vide. + + La fonction suppose la liste des semestres triées par ordre + décroissant de date. + + Args: + semestres: une liste de ``FormSemestre`` + nbre_etapes_max: le nombre d'étapes max prise en compte + + Returns: + Une liste de nom de semestre (dans le même ordre que les ``semestres``) + + See also: + app.pe.pe_affichage.nom_semestre_etape + """ + assert len(semestres) <= nbre_etapes_max + + noms = [nom_semestre_etape(sem, avec_fid=False) for (fid, sem) in semestres.items()] + noms = noms[::-1] # trie par ordre croissant + + dico = {f"Etape {i+1}": "" for i in range(nbre_etapes_max)} + for i, nom in enumerate(noms): # Charge les noms de semestres + dico[f"Etape {i+1}"] = nom + return dico + + +def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str: + """Nom d'un semestre à afficher dans le descriptif des étapes de la scolarité + d'un étudiant. + + Par ex: Pour un S2, affiche ``"Semestre 2 FI S014-2015 (129)"`` avec : + + * 2 le numéro du semestre, + * FI la modalité, + * 2014-2015 les dates + + Args: + semestre: Un ``FormSemestre`` + avec_fid: Ajoute le n° du semestre à la description + + Returns: + La chaine de caractères décrivant succintement le semestre + """ + formation: Formation = semestre.formation + parcours = codes_cursus.get_cursus_from_code(formation.type_parcours) + + description = [ + parcours.SESSION_NAME.capitalize(), + str(semestre.semestre_id), + semestre.modalite, # eg FI ou FC + f"{semestre.date_debut.year}-{semestre.date_fin.year}", + ] + if avec_fid: + description.append(f"({semestre.formsemestre_id})") + + return " ".join(description) diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index 2ac91962..7c45d4c8 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -46,6 +46,12 @@ import io import os from zipfile import ZipFile +import numpy as np + +from app.pe import pe_comp +from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE + +from app.pe.pe_tabletags import TableTag from app.scodoc.gen_tables import SeqGenTable from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_trajectoire import TrajectoiresJuryPE, Trajectoire @@ -116,7 +122,9 @@ class JuryPE(object): self._gen_xls_semestre_taggues(zipfile) self._gen_xls_trajectoires(zipfile) self._gen_xls_aggregats(zipfile) - self._gen_xls_synthese(zipfile) + self._gen_xls_synthese_jury_par_tag(zipfile) + self._gen_xls_synthese_par_etudiant(zipfile) + # Fin !!!! Tada :) @@ -234,13 +242,13 @@ class JuryPE(object): path="details", ) - def _gen_xls_synthese(self, zipfile: ZipFile): - """Synthèse des éléments du jury PE""" + def _gen_xls_synthese_jury_par_tag(self, zipfile: ZipFile): + """Synthèse des éléments du jury PE tag par tag""" # Synthèse des éléments du jury PE - self.synthese = self.synthetise_juryPE() + self.synthese = self.synthetise_jury_par_tags() # Export des données => mode 1 seule feuille -> supprimé - pe_affichage.pe_print("*** Export du jury de synthese") + pe_affichage.pe_print("*** Export du jury de synthese par tags") output = io.BytesIO() with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated output, engine="openpyxl" @@ -251,7 +259,27 @@ class JuryPE(object): output.seek(0) self.add_file_to_zip( - zipfile, f"synthese_jury_{self.diplome}.xlsx", output.read() + zipfile, f"synthese_jury_{self.diplome}_par_tag.xlsx", output.read() + ) + + def _gen_xls_synthese_par_etudiant(self, zipfile: ZipFile): + """Synthèse des éléments du jury PE, étudiant par étudiant""" + # Synthèse des éléments du jury PE + synthese = self.synthetise_jury_par_etudiants() + + # Export des données => mode 1 seule feuille -> supprimé + pe_affichage.pe_print("*** Export du jury de synthese par étudiants") + output = io.BytesIO() + with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated + output, engine="openpyxl" + ) as writer: + for onglet, df in synthese.items(): + # écriture dans l'onglet: + df.to_excel(writer, onglet, index=True, header=True) + output.seek(0) + + self.add_file_to_zip( + zipfile, f"synthese_jury_{self.diplome}_par_etudiant.xlsx", output.read() ) def add_file_to_zip(self, zipfile: ZipFile, filename: str, data, path=""): @@ -289,10 +317,11 @@ class JuryPE(object): # Méthodes pour la synthèse du juryPE # ***************************************************************************************************************** - def synthetise_juryPE(self): - """Synthétise tous les résultats du jury PE dans des dataframes""" + def synthetise_jury_par_tags(self) -> dict[pd.DataFrame]: + """Synthétise tous les résultats du jury PE dans des dataframes, + dont les onglets sont les tags""" - pe_affichage.pe_print("*** Synthèse finale des moyennes ***") + pe_affichage.pe_print("*** Synthèse finale des moyennes par tag***") synthese = {} pe_affichage.pe_print(" -> Synthèse des données administratives") @@ -323,56 +352,108 @@ class JuryPE(object): for etudid in etudids: etudiant = self.etudiants.identites[etudid] donnees[etudid] = { - "Nom": etudiant.nom, - "Prenom": etudiant.prenom, - "Civilite": etudiant.civilite_str, + ("Identité", "", "Civilite"): etudiant.civilite_str, + ("Identité", "", "Nom"): etudiant.nom, + ("Identité", "", "Prenom"): etudiant.prenom, } for aggregat in aggregats: + # Le dictionnaire par défaut des moyennes + donnees[etudid] |= get_defaut_dict_synthese_aggregat(aggregat, self.diplome) + # La trajectoire de l'étudiant sur l'aggrégat trajectoire = self.trajectoires.suivi[etudid][aggregat] - - # Les moyennes par tag de cette trajectoire - donnees[etudid] |= { - f"{aggregat} notes ": "-", - f"{aggregat} class. (groupe)": "-", - f"{aggregat} min | moy | max (groupe)": "-", - } if trajectoire: trajectoire_tagguee = self.trajectoires_tagguees[ trajectoire.trajectoire_id ] - if tag in trajectoire_tagguee.moyennes_tags: - bilan = trajectoire_tagguee.moyennes_tags[tag] + else: + trajectoire_tagguee = None - donnees[etudid] |= { - 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}", - } - - """L'interclassement""" + # L'interclassement interclass = self.interclassements_taggues[aggregat] - donnees[etudid] |= { - f"{aggregat} class. (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}", - } + # Injection des données dans un dictionnaire + donnees[etudid] |= get_dict_synthese_aggregat(aggregat, trajectoire_tagguee, interclass, etudid, tag, self.diplome) + + # Fin de l'aggrégat # Construction du dataFrame df = pd.DataFrame.from_dict(donnees, orient="index") # Tri par nom/prénom - df.sort_values(by=["Nom", "Prenom"], inplace=True) + df.sort_values( + by=[("Identité", "", "Nom"), ("Identité", "", "Prenom")], inplace=True + ) return df + def synthetise_jury_par_etudiants(self) -> dict[pd.DataFrame]: + """Synthétise tous les résultats du jury PE dans des dataframes, + dont les onglets sont les étudiants""" + pe_affichage.pe_print("*** Synthèse finale des moyennes par étudiants***") + + synthese = {} + pe_affichage.pe_print(" -> Synthèse des données administratives") + synthese["administratif"] = self.etudiants.df_administratif(self.diplomes_ids) + + etudids = list(self.diplomes_ids) + + for etudid in etudids: + etudiant = self.etudiants.identites[etudid] + nom = etudiant.nom + prenom = etudiant.prenom[0] # initial du prénom + + onglet = f"{nom} {prenom}. ({etudid})" + if len(onglet) > 32: # limite sur la taille des onglets + fin_onglet = f"{prenom}. ({etudid})" + onglet = f"{nom[:32-len(fin_onglet)-2]}." + fin_onglet + + pe_affichage.pe_print(f" -> Synthèse de l'étudiant {etudid}") + synthese[onglet] = self.df_synthese_etudiant(etudid) + return synthese + + def df_synthese_etudiant(self, etudid: int) -> pd.DataFrame: + """Créé un DataFrame pour un étudiant donné par son etudid, retraçant + toutes ses moyennes aux différents tag et aggrégats""" + tags = self.do_tags_list(self.interclassements_taggues) + aggregats = pe_comp.TOUS_LES_PARCOURS + + donnees = {} + + for tag in tags: + # Une ligne pour le tag + donnees[tag] = {("", "", "tag"): tag} + + for aggregat in aggregats: + # Le dictionnaire par défaut des moyennes + donnees[tag] |= get_defaut_dict_synthese_aggregat(aggregat, self.diplome) + + # La trajectoire de l'étudiant sur l'aggrégat + trajectoire = self.trajectoires.suivi[etudid][aggregat] + if trajectoire: + trajectoire_tagguee = self.trajectoires_tagguees[ + trajectoire.trajectoire_id + ] + else: + trajectoire_tagguee = None + + # L'interclassement + interclass = self.interclassements_taggues[aggregat] + + # Injection des données dans un dictionnaire + donnees[tag] |= get_dict_synthese_aggregat(aggregat, trajectoire_tagguee, interclass, etudid, tag, self.diplome) + + + # Fin de l'aggrégat + # Construction du dataFrame + df = pd.DataFrame.from_dict(donnees, orient="index") + + # Tri par nom/prénom + df.sort_values( + by=[("", "", "tag")], inplace=True + ) + return df def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict: """Créé les semestres taggués, de type 'S1', 'S2', ..., pour un groupe d'étudiants donnés. @@ -466,3 +547,122 @@ def compute_interclassements( ) aggregats_interclasses_taggues[nom_aggregat] = interclass return aggregats_interclasses_taggues + + +def get_defaut_dict_synthese_aggregat(aggregat: str, diplome: int) -> dict: + """Renvoie le dictionnaire de synthèse (à intégrer dans + un tableur excel) pour décrire les résultats d'un aggrégat""" + # L'affichage de l'aggrégat dans le tableur excel + descr = pe_comp.PARCOURS[aggregat]["descr"] + + nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}" + donnees = { + (descr, "", "note"): SANS_NOTE, + # Les stat du groupe + (descr, NOM_STAT_GROUPE, "class."): SANS_NOTE, + (descr, NOM_STAT_GROUPE, "min"): SANS_NOTE, + (descr, NOM_STAT_GROUPE, "moy"): SANS_NOTE, + (descr, NOM_STAT_GROUPE, "max"): SANS_NOTE, + # Les stats de l'interclassement dans la promo + (descr, nom_stat_promo, "class."): SANS_NOTE, + ( + descr, + nom_stat_promo, + "min", + ): SANS_NOTE, + ( + descr, + nom_stat_promo, + "moy", + ): SANS_NOTE, + ( + descr, + nom_stat_promo, + "max", + ): SANS_NOTE, + } + return donnees + + +def get_dict_synthese_aggregat( + aggregat: str, + trajectoire_tagguee: TrajectoireTag, + interclassement_taggue: AggregatInterclasseTag, + etudid: int, + tag: str, + diplome: int +): + """Renvoie le dictionnaire (à intégrer au tableur excel de synthese) + traduisant les résultats (moy/class) d'un étudiant à une trajectoire tagguée associée + à l'aggrégat donné et pour un tag donné""" + donnees = {} + # L'affichage de l'aggrégat dans le tableur excel + descr = pe_comp.PARCOURS[aggregat]["descr"] + + # La note de l'étudiant (chargement à venir) + note = np.nan + + # Les données de la trajectoire tagguée pour le tag considéré + if trajectoire_tagguee and tag in trajectoire_tagguee.moyennes_tags: + bilan = trajectoire_tagguee.moyennes_tags[tag] + + # La moyenne de l'étudiant + note = TableTag.get_note_for_df(bilan, etudid) + + # Statistiques sur le groupe + if note != np.nan: + # Les moyennes de cette trajectoire + donnees |= { + (descr, "", "note"): note, + ( + descr, + NOM_STAT_GROUPE, + "class.", + ): TableTag.get_class_for_df(bilan, etudid), + ( + descr, + NOM_STAT_GROUPE, + "min", + ): TableTag.get_min_for_df(bilan), + ( + descr, + NOM_STAT_GROUPE, + "moy", + ): TableTag.get_moy_for_df(bilan), + ( + descr, + NOM_STAT_GROUPE, + "max", + ): TableTag.get_max_for_df(bilan), + } + + # L'interclassement + if tag in interclassement_taggue.moyennes_tags: + bilan = interclassement_taggue.moyennes_tags[tag] + + if note != np.nan: + nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}" + + donnees |= { + ( + descr, + nom_stat_promo, + "class.", + ): TableTag.get_class_for_df(bilan, etudid), + ( + descr, + nom_stat_promo, + "min", + ): TableTag.get_min_for_df(bilan), + ( + descr, + nom_stat_promo, + "moy", + ): TableTag.get_moy_for_df(bilan), + ( + descr, + nom_stat_promo, + "max", + ): TableTag.get_max_for_df(bilan), + } + return donnees diff --git a/app/pe/pe_semtag.py b/app/pe/pe_semtag.py index d77f1caf..0b8a6238 100644 --- a/app/pe/pe_semtag.py +++ b/app/pe/pe_semtag.py @@ -35,7 +35,7 @@ Created on Fri Sep 9 09:15:05 2016 @author: barasc """ - +import app.pe.pe_etudiant from app import db, log from app.comp import res_sem, moy_ue, moy_sem from app.comp.res_compat import NotesTableCompat @@ -133,7 +133,7 @@ class SemestreTag(TableTag): def get_repr(self): """Nom affiché pour le semestre taggué""" - return pe_affichage.nom_semestre_etape(self.formsemestre, avec_fid=True) + return app.pe.pe_etudiant.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é, diff --git a/app/pe/pe_tabletags.py b/app/pe/pe_tabletags.py index ae4a7fe4..9df18c01 100644 --- a/app/pe/pe_tabletags.py +++ b/app/pe/pe_tabletags.py @@ -48,8 +48,6 @@ TAGS_RESERVES = ["but"] class TableTag(object): - - def __init__(self): """Classe centralisant différentes méthodes communes aux SemestreTag, TrajectoireTag, AggregatInterclassTag @@ -105,3 +103,36 @@ class TableTag(object): df = df.join(self.moyennes_tags[tag]["classements"].rename(f"class {tag}")) return df.to_csv(sep=";") + + @classmethod + def get_min_for_df(cls, bilan: dict) -> float: + """Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné, + revoie le min renseigné pour affichage dans un df""" + return round(bilan["min"], 2) + + @classmethod + def get_max_for_df(cls, bilan: dict) -> float: + """Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné, + renvoie le max renseigné pour affichage dans un df""" + return round(bilan["max"], 2) + + @classmethod + def get_moy_for_df(cls, bilan: dict) -> float: + """Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné, + renvoie la moyenne renseignée pour affichage dans un df""" + return round(bilan["moy"], 2) + + @classmethod + def get_class_for_df(cls, bilan: dict, etudid: int) -> str: + """Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné, + renvoie le classement ramené au nombre d'inscrits, + pour un étudiant donné par son etudid""" + return f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}" + + + @classmethod + def get_note_for_df(cls, bilan: dict, etudid: int): + """Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné, + renvoie la note (moyenne) + pour un étudiant donné par son etudid""" + return round(bilan["notes"].loc[etudid], 2) \ No newline at end of file