diff --git a/app/pe/moys/pe_interclasstag.py b/app/pe/moys/pe_interclasstag.py index 0dfd25f2..bc4bb6b2 100644 --- a/app/pe/moys/pe_interclasstag.py +++ b/app/pe/moys/pe_interclasstag.py @@ -152,11 +152,14 @@ class InterClassTag(pe_tabletags.TableTag): aff = pe_affichage.repr_profil_coeffs(coeffs, with_index=True) pe_affichage.pe_print(f"--> Moyenne 👜{tag} avec coeffs: {aff} ") + infos = {"aggregat": self.nom_rcs, "cohorte": pe_moytag.CHAMP_PROMO} + self.moyennes_tags[tag] = pe_moytag.MoyennesTag( tag, self.type, notes_gen, coeffs, # limite les moyennes aux Ă©tudiants de la promo + infos, ) def get_repr(self) -> str: @@ -321,9 +324,7 @@ class InterClassTag(pe_tabletags.TableTag): # Charge la moyenne if tag in rcstag.moyennes_tags: moytag: pd.DataFrame = rcstag.moyennes_tags[tag] - df_moytag = moytag.to_df( - aggregat=aggregat, cohorte="Groupe", options=options - ) + df_moytag = moytag.to_df(options=options) # Modif les colonnes au regard du 1er df_moytag significatif lu if not initialisation: diff --git a/app/pe/moys/pe_moy.py b/app/pe/moys/pe_moy.py index 20e1a268..6dd4ef5c 100644 --- a/app/pe/moys/pe_moy.py +++ b/app/pe/moys/pe_moy.py @@ -20,21 +20,30 @@ class Moyenne: @classmethod def get_colonnes_synthese(cls, with_min_max_moy): + """Renvoie le nom des colonnes Ă  prendre en compte pour la gĂ©nĂ©ration + d'un dataFrame rĂ©sumant les donnĂ©es d'un objet pe_moy.Moyenne""" if with_min_max_moy: return ["note", "rang", "min", "max", "moy"] else: return ["note", "rang"] - def __init__(self, notes: pd.Series): - """Classe centralisant la synthĂšse des moyennes/classements d'une sĂ©rie - de notes : + def __init__(self, notes: pd.Series, infos: dict[str]): + """Classe centralisant la synthĂšse des moyennes/class/stat d'une sĂ©rie + de notes pour un groupe d'Ă©tudiants (dĂ©duits des notes). - * des "notes" : la Serie pandas des notes (float), - * des "classements" : la Serie pandas des classements (float), - * des "min" : la note minimum, - * des "max" : la note maximum, - * des "moy" : la moyenne, - * des "nb_inscrits" : le nombre d'Ă©tudiants ayant une note, + Sont gĂ©nerĂ©s des SĂ©ries/DataFrame donnant : + + * les "notes" : notes (float), + * des "classements" : classements (float), + * des "min" : la note minimum sur tout le groupe d'Ă©tudiants, + * des "max" : la note maximum sur tout le groupe d'Ă©tudiants, + * des "moy" : la moyenne des notes sur tout le groupe d'Ă©tudiants, + * des "nb_inscrits" : le nombre d'Ă©tudiants ayant une note (non NaN) + + Args: + notes: Une (pandas.)SĂ©rie de notes + infos: Un dictionnaire donnant les informations sur la moyenne (aggrĂ©gat, + tag, intitule, cohorte, groupe) """ self.notes = notes """Les notes""" @@ -44,9 +53,28 @@ class Moyenne: """Les id des Ă©tudiants dont la note est non nan/renseignĂ©e""" self.df: pd.DataFrame = self.comp_moy_et_stat(self.notes) """Le dataframe retraçant les moyennes/classements/statistiques""" + self.infos = { + "aggregat": infos["aggregat"], + "tag": infos["tag"], + "intitule": infos["intitule"], + "cohorte": infos["cohorte"], + } + """Dictionnaire donnant des informations sur la note (aggrĂ©gat, cohorte, tag, type_de_moyenne)""" # self.synthese = self.to_dict() # """La synthĂšse (dictionnaire) des notes/classements/statistiques""" + def __repr__(self): + """ReprĂ©sentation textuelle d'un objet Moyenne + sur la base de ses `infos`. + """ + repr = get_repr( + self.infos["aggregat"], + self.infos["tag"], + self.infos["intitule"], + self.infos["cohorte"], + ) + return f"Moyenne {repr}" + def comp_moy_et_stat(self, notes: pd.Series) -> dict: """Calcule et structure les donnĂ©es nĂ©cessaires au PE pour une sĂ©rie de notes (pouvant ĂȘtre une moyenne d'un tag Ă  une UE ou une moyenne gĂ©nĂ©rale @@ -103,14 +131,29 @@ class Moyenne: return df def to_df(self, with_min_max_moy=None): - """Renvoie le df de synthĂšse, en limitant les colonnes Ă  celles attendues - (dĂ©pendantes de l'option `with_min_max_moy`) + """Renvoie le df de synthĂšse (tel qu'attendu dans les exports Excel), + en limitant les colonnes Ă  celles attendues (dĂ©pendantes de l'option + ``with_min_max_moy``) """ colonnes_synthese = Moyenne.get_colonnes_synthese( with_min_max_moy=with_min_max_moy ) + # Copie le df modĂ©lisant les donnĂ©es df = self.df[colonnes_synthese].copy() df["rang"] = df["rang"].replace("nan", "") + + # Remplace les noms de colonnes par leur intitulĂ© dans le tableur excel + cols = [] + for critere in colonnes_synthese: + nom_col = get_colonne_df( + self.infos["aggregat"], + self.infos["tag"], + self.infos["intitule"], # UEs ou compĂ©tences + self.infos["cohorte"], + critere, + ) + cols += [nom_col] + df.columns = cols return df def to_json(self) -> dict: @@ -122,3 +165,39 @@ class Moyenne: def has_notes(self) -> bool: """Indique si la moyenne est significative (c'est-Ă -dire Ă  des notes) et/ou des inscrits""" return len(self.inscrits_ids) > 0 + + +def get_repr(aggregat, tag, intitule, cohorte): + """Renvoie une reprĂ©sentation textuelle "aggregat|tag|intitule|cohorte" + pour reprĂ©senter une moyenne + """ + liste_champs = [] + if aggregat != None: + liste_champs += [aggregat] + liste_champs += [tag, intitule] + if cohorte != None: + liste_champs += [cohorte] + return "|".join(liste_champs) + + +def get_colonne_df(aggregat, tag, intitule, cohorte, critere): + """Renvoie la chaine de caractĂšre "aggregat|tag|intitule|cohorte|critere" + utilisĂ© pour dĂ©signer les colonnes du df. + + Args: + aggregat: Un nom d'aggrĂ©gat (gĂ©nĂ©ralement "S1" ou "3S") + pouvant ĂȘtre optionnel (si `None`) + tag: Un nom de tags (par ex. "maths") + intitule: Un nom d'UE ou de compĂ©tences ou "GĂ©nĂ©ral" + cohorte: Une cohorte pour les interclassements (gĂ©nĂ©ralement + Groupe ou Promo + pouvant ĂȘtre optionnel (si `None`) + critere: Un critĂšre correspondant Ă  l'une des colonnes + d'une pe_moy.Moyenne + Returns: + Une chaine de caractĂšres indiquant les champs sĂ©parĂ©s par + un ``"|"``, gĂ©nĂ©ralement de la forme + "S1|maths|UE|Groupe|note" + """ + liste_champs = [get_repr(aggregat, tag, intitule, cohorte), critere] + return "|".join(liste_champs) diff --git a/app/pe/moys/pe_moytag.py b/app/pe/moys/pe_moytag.py index 34ce2050..f2e96940 100644 --- a/app/pe/moys/pe_moytag.py +++ b/app/pe/moys/pe_moytag.py @@ -4,11 +4,14 @@ import pandas as pd from app import comp from app.comp.moy_sem import comp_ranks_series from app.pe.moys import pe_moy -from app.scodoc.sco_utils import ModuleType +from app.pe.moys.pe_moy import get_colonne_df +import re CODE_MOY_UE = "UEs" CODE_MOY_COMPETENCES = "CompĂ©tences" CHAMP_GENERAL = "GĂ©nĂ©ral" # Nom du champ de la moyenne gĂ©nĂ©rale +CHAMP_GROUPE = "groupe" +CHAMP_PROMO = "promo" class MoyennesTag: @@ -18,18 +21,22 @@ class MoyennesTag: type_moyenne: str, matrice_notes: pd.DataFrame, # etudids x UEs|comp matrice_coeffs: pd.DataFrame, # etudids x UEs|comp + infos: dict, ): - """Classe centralisant la synthĂšse des moyennes/classements d'une sĂ©rie - d'Ă©tudiants Ă  un tag donnĂ©, en diffĂ©renciant les notes - obtenues aux UE et au gĂ©nĂ©ral (toutes UEs confondues) + """Classe centralisant un ensemble de moyennes/class/stat, + obtenu par un groupe d'Ă©tudiants, Ă  un tag donnĂ©, + en stockant les moyennes aux UEs|CompĂ©tences + et la moyenne gĂ©nĂ©rale (toutes UEs confondues). + Args: tag: Un tag matrice_notes: Les moyennes (etudid x acronymes_ues|compĂ©tences) - aux diffĂ©rentes UEs ou compĂ©tences + aux diffĂ©rentes UEs ou compĂ©tences matrice_coeffs: Les coeffs (etudid x acronymes_ues|compĂ©tences) aux diffĂ©rentes UEs ou compĂ©tences + infos: Informations (aggrĂ©gat, cohorte ayant servi Ă  calculer les moyennes) """ self.tag = tag """Le tag associĂ© aux moyennes""" @@ -37,6 +44,13 @@ class MoyennesTag: self.type = type_moyenne """Le type de moyennes (par UEs ou par compĂ©tences)""" + self.infos = { + "aggregat": infos["aggregat"], + "tag": tag, + "cohorte": infos["cohorte"], + } + """Info sur les Ă©lĂ©ments (aggrĂ©gat, cohorte) ayant servi Ă  calculer les moyennes""" + # Les moyennes par UE/compĂ©tences (ressources/SAEs confondues) self.matrice_notes: pd.DataFrame = matrice_notes """Les notes par UEs ou CompĂ©tences (DataFrame etudids x UEs|comp)""" @@ -45,10 +59,10 @@ class MoyennesTag: """Les coeffs Ă  appliquer pour le calcul des moyennes gĂ©nĂ©rales (toutes UE ou compĂ©tences confondues). NaN si Ă©tudiant non inscrit""" - self.champs: list[str] = list(self.matrice_notes.columns) - """Les champs (acronymes d'UE ou compĂ©tences) renseignĂ©s dans les moyennes""" - assert len(self.champs) == len( - set(self.champs) + self.intitules: list[str] = list(self.matrice_notes.columns) + """Les intitules (acronymes d'UE ou compĂ©tences) renseignĂ©s dans les moyennes""" + assert len(self.intitules) == len( + set(self.intitules) ), "Des champs de moyennes en doublons" self.etudids: list[int] = list(self.matrice_notes.index) @@ -56,10 +70,11 @@ class MoyennesTag: self.moyennes_dict: dict[str, pe_moy.Moyenne] = {} """Dictionnaire associant Ă  chaque UE|CompĂ©tence ses donnĂ©es moyenne/class/stat""" - for col in self.champs: # if ue.type != UE_SPORT: + for col in self.intitules: # if ue.type != UE_SPORT: # Les moyennes tous modules confondus notes = matrice_notes[col] - self.moyennes_dict[col] = pe_moy.Moyenne(notes) + infos = self.infos | {"intitule": col} + self.moyennes_dict[col] = pe_moy.Moyenne(notes, infos) # Les moyennes gĂ©nĂ©rales (toutes UEs confondues) self.notes_gen = pd.Series(np.nan, index=self.matrice_notes.index) @@ -68,7 +83,8 @@ class MoyennesTag: self.notes_gen = self.compute_moy_gen( self.matrice_notes, self.matrice_coeffs ) - self.moyenne_gen = pe_moy.Moyenne(self.notes_gen) + infos = self.infos | {"intitule": CHAMP_GENERAL} + self.moyenne_gen = pe_moy.Moyenne(self.notes_gen, infos) """Dataframe retraçant les moyennes/classements/statistiques gĂ©nĂ©ral (toutes UESs confondues et modules confondus)""" def has_notes(self): @@ -114,16 +130,13 @@ class MoyennesTag: "Pb dans le calcul de la moyenne toutes UEs/compĂ©tences confondues" ) - def to_df( - self, aggregat=None, cohorte=None, options={"min_max_moy": True} - ) -> pd.DataFrame: + def to_df(self, options={"min_max_moy": True}) -> pd.DataFrame: """Renvoie le df synthĂ©tisant l'ensemble des donnĂ©es connues. - Adapte les intitulĂ©s des colonnes aux donnĂ©es fournies - (nom d'aggrĂ©gat, type de cohorte). - Args: - aggregat: Le nom de l'aggrĂ©gat (Ă©ventuellement `None` si non connu) - cohorte: La cohorte Groupe ou Promo (Ă©ventuellement `None` si non connue) + Adapte : + * les noms des colonnes aux donnĂ©es fournies dans l'attribut + ``infos`` (nom d'aggrĂ©gat, type de cohorte). + * Ă  l'option ``min_max_moy`` (limitant les colonnes) """ if "min_max_moy" not in options or options["min_max_moy"]: with_min_max_moy = True @@ -137,60 +150,61 @@ class MoyennesTag: df = pd.DataFrame(index=etudids_sorted) # Ajout des notes pour tous les champs - champs = list(self.champs) + champs = list(self.intitules) for champ in champs: moy: pe_moy.Moyenne = self.moyennes_dict[champ] - df_champ = moy.to_df(with_min_max_moy=with_min_max_moy) # le dataframe - # Renomme les colonnes - - cols = [ - get_colonne_df(aggregat, self.tag, champ, cohorte, critere) - for critere in pe_moy.Moyenne.get_colonnes_synthese( - with_min_max_moy=with_min_max_moy - ) - ] - df_champ.columns = cols + df_champ = moy.to_df( + with_min_max_moy=with_min_max_moy + ) # le dataframe (les colonnes ayant Ă©tĂ© renommĂ©es) + colonnes_renommees = ajout_numero_a_colonnes( + list(df.columns), list(df_champ.columns) + ) + if colonnes_renommees: + df_champ.columns = colonnes_renommees df = df.join(df_champ) # Ajoute la moy gĂ©nĂ©rale df_moy_gen = self.moyenne_gen.to_df(with_min_max_moy=with_min_max_moy) - cols = [ - get_colonne_df(aggregat, self.tag, CHAMP_GENERAL, cohorte, critere) - for critere in pe_moy.Moyenne.get_colonnes_synthese( - with_min_max_moy=with_min_max_moy - ) - ] - df_moy_gen.columns = cols + colonnes_renommees = ajout_numero_a_colonnes( + list(df.columns), list(df_moy_gen.columns) + ) + if colonnes_renommees: + df_moy_gen.columns = colonnes_renommees df = df.join(df_moy_gen) return df -def get_colonne_df(aggregat, tag, champ, cohorte, critere): - """Renvoie le tuple (aggregat, tag, champ, cohorte, critere) - utilisĂ© pour dĂ©signer les colonnes du df. +def ajout_numero_a_colonnes(colonnes, colonnes_a_ajouter): + """Partant d'une liste de noms de colonnes, vĂ©rifie si les noms des colonnes_a_ajouter + n'entre pas en conflit (aka ne sont pas dĂ©jĂ  prĂ©sent dans colonnes). + Si nom, renvoie `None`. + Si oui, propose une liste de noms de colonnes_a_ajouter dans laquelle chaque nom + est suivi d'un `"(X)"` (oĂč X est un numĂ©ro choisi au regard des noms de colonnes). + Les noms des colonnes sont de la forme "S1|maths|UE|Groupe|note (1)" - Args: - aggregat: Un nom d'aggrĂ©gat (gĂ©nĂ©ralement "S1" ou "3S") - pouvant ĂȘtre optionnel (si `None`) - tag: Un nom de tags (par ex. "maths") - champ: Un nom d'UE ou de compĂ©tences - cohorte: Une cohorte pour les interclassements (gĂ©nĂ©ralement - Groupe ou Promo - pouvant ĂȘtre optionnel (si `None`) - critere: Un critĂšre correspondant Ă  l'une des colonnes - d'une pe_moy.Moyenne - Returns: - Une chaine de caractĂšres indiquant les champs sĂ©parĂ©s par - un ``"|"``, gĂ©nĂ©ralement de la forme - "S1|maths|UE|Groupe|note" + Devrait ĂȘtre supprimĂ© Ă  terme, car les noms des colonnes sont thĂ©oriquement prĂ©vus pour ĂȘtre + unique/sans doublons. """ - liste_champs = [] - if aggregat != None: - liste_champs += [aggregat] + assert len(colonnes) == len(set(colonnes)), "Il y a dĂ©jĂ  des doublons dans colonnes" + colonnes_sans_numero = [col.split(" (")[0] for col in colonnes] - liste_champs += [tag, champ] - if cohorte != None: - liste_champs += [cohorte] - liste_champs += [critere] - return "|".join(liste_champs) + conflits = set(colonnes_sans_numero).intersection(colonnes_a_ajouter) + if not conflits: + # Pas de conflit + return None + + pattern = r"\((\d*)\)" + p = re.compile(pattern) + numeros = [] + for col in colonnes: + numeros.extend(p.findall(col)) + + if numeros: + numeros = [int(num) for num in numeros] + num_max = max(numeros) + else: + num_max = 0 + + ajouts = [f"{col} ({num_max+1})" for col in colonnes_a_ajouter] + return ajouts diff --git a/app/pe/moys/pe_rcstag.py b/app/pe/moys/pe_rcstag.py index aa82392a..f67097b5 100644 --- a/app/pe/moys/pe_rcstag.py +++ b/app/pe/moys/pe_rcstag.py @@ -101,7 +101,7 @@ class RCSemXTag(pe_tabletags.TableTag): self.sxtags_connus = sxstags # Tous les sxstags connus # Les Ă©tudiants (etuds, Ă©tats civils & etudis) - sems_dans_aggregat = rcsemx.aggregat + sems_dans_aggregat = rcsemx.noms_semestres_aggreges sxtag_final = self.sxstags_aggreges[(sems_dans_aggregat[-1], self.rcs_id[1])] self.etuds = sxtag_final.etuds """Les Ă©tudiants (extraits du semestre final)""" @@ -162,11 +162,13 @@ class RCSemXTag(pe_tabletags.TableTag): pe_affichage.pe_print(f" > Moyenne calculĂ©e avec pour coeffs : {aff}") # MĂ©morise les moyennes et les coeff associĂ©s + infos = {"aggregat": self.rcs_id[0], "cohorte": pe_moytag.CHAMP_GROUPE} self.moyennes_tags[tag] = pe_moytag.MoyennesTag( tag, pe_moytag.CODE_MOY_COMPETENCES, moys_competences, matrice_coeffs_moy_gen, + infos, ) def __eq__(self, other): diff --git a/app/pe/moys/pe_ressemtag.py b/app/pe/moys/pe_ressemtag.py index c1ec6eb0..7b0148a2 100644 --- a/app/pe/moys/pe_ressemtag.py +++ b/app/pe/moys/pe_ressemtag.py @@ -179,20 +179,20 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): self.ues_inscr_parcours_df, info_tag=info_tag, pole=None ) # MĂ©morise les moyennes + infos = {"aggregat": None, "cohorte": pe_moytag.CHAMP_GROUPE} self.moyennes_tags[tag] = pe_moytag.MoyennesTag( tag, pe_moytag.CODE_MOY_UE, moy_ues_tag, self.matrice_coeffs_moy_gen, + infos, ) # Ajoute les moyennes par UEs + la moyenne gĂ©nĂ©rale (but) moy_gen = self.compute_moy_gen(self.acro_ues_inscr_parcours) + infos = {"aggregat": None, "cohorte": pe_moytag.CHAMP_GROUPE} self.moyennes_tags["but"] = pe_moytag.MoyennesTag( - "but", - pe_moytag.CODE_MOY_UE, - moy_gen, - self.matrice_coeffs_moy_gen, + "but", pe_moytag.CODE_MOY_UE, moy_gen, self.matrice_coeffs_moy_gen, infos ) # Ajoute la moyenne gĂ©nĂ©rale par ressources @@ -200,11 +200,13 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): moy_res_gen = self.compute_moy_ues_tag( self.ues_inscr_parcours_df, info_tag=None, pole=ModuleType.RESSOURCE ) + infos = {"aggregat": None, "cohorte": pe_moytag.CHAMP_GROUPE} self.moyennes_tags["ressources"] = pe_moytag.MoyennesTag( "ressources", pe_moytag.CODE_MOY_UE, moy_res_gen, self.matrice_coeffs_moy_gen, + infos, ) # Ajoute la moyenne gĂ©nĂ©rale par saes @@ -212,11 +214,13 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): moy_saes_gen = self.compute_moy_ues_tag( self.ues_inscr_parcours_df, info_tag=None, pole=ModuleType.SAE ) + infos = {"aggregat": None, "cohorte": pe_moytag.CHAMP_GROUPE} self.moyennes_tags["saes"] = pe_moytag.MoyennesTag( "saes", pe_moytag.CODE_MOY_UE, moy_saes_gen, self.matrice_coeffs_moy_gen, + infos, ) # Tous les tags diff --git a/app/pe/moys/pe_sxtag.py b/app/pe/moys/pe_sxtag.py index 6c11ea1a..9c94aeef 100644 --- a/app/pe/moys/pe_sxtag.py +++ b/app/pe/moys/pe_sxtag.py @@ -211,11 +211,13 @@ class SxTag(pe_tabletags.TableTag): ) # MĂ©morise les infos pour la moyenne au tag + infos = {"aggregat": self.sxtag_id[0], "cohorte": pe_moytag.CHAMP_GROUPE} self.moyennes_tags[tag] = pe_moytag.MoyennesTag( tag, pe_moytag.CODE_MOY_UE, matrice_moys_ues, self.matrice_coeffs_moy_gen, + infos, ) # Affichage de debug diff --git a/app/pe/moys/pe_tabletags.py b/app/pe/moys/pe_tabletags.py index 0ea7747d..e49972d4 100644 --- a/app/pe/moys/pe_tabletags.py +++ b/app/pe/moys/pe_tabletags.py @@ -90,7 +90,7 @@ class TableTag(object): options={"min_max_moy": True}, ) -> pd.DataFrame: """Renvoie un dataframe listant toutes les donnĂ©es - des moyennes/classements/nb_inscrits/min/max/moy + des moyennes/classements/min/max/moy des Ă©tudiants aux diffĂ©rents tags. tags_cibles limitent le dataframe aux tags indiquĂ©s @@ -122,9 +122,7 @@ class TableTag(object): # Ajout des donnĂ©es par tags for tag in tags_cibles: if tag in self.moyennes_tags: - moy_tag_df = self.moyennes_tags[tag].to_df( - aggregat=aggregat, cohorte=cohorte, options=options - ) + moy_tag_df = self.moyennes_tags[tag].to_df(options=options) df = df.join(moy_tag_df) # Tri par nom, prĂ©nom diff --git a/app/pe/pe_affichage.py b/app/pe/pe_affichage.py index a84bd372..b110121a 100644 --- a/app/pe/pe_affichage.py +++ b/app/pe/pe_affichage.py @@ -11,7 +11,7 @@ from app import log from app.pe.rcss import pe_rcs import app.pe.pe_comp as pe_comp -PE_DEBUG = False +PE_DEBUG = True # On stocke les logs PE dans g.scodoc_pe_log diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index d06df6c4..3781d9aa 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -51,6 +51,8 @@ import numpy as np import pandas as pd import jinja2 +import app.pe.moys.pe_moy +import app.pe.moys.pe_moytag as pe_moytag from app.pe.rcss import pe_rcs from app.pe.moys import pe_sxtag @@ -730,20 +732,32 @@ class JuryPE(object): ) if not df_groupe.empty: aff_aggregat += [aggregat + " (Groupe)"] + + colonnes_renommees = pe_moytag.ajout_numero_a_colonnes( + list(df.columns), list(df_groupe.columns) + ) + if colonnes_renommees: + df_groupe.columns = colonnes_renommees + df = df.join(df_groupe) - # Le dataframe du classement sur la promo - df_promo = interclass.to_df( - administratif=False, - aggregat=aggregat, - tags_cibles=[tag], - cohorte="Promo", - options=self.options, - ) + # Le dataframe du classement sur la promo + df_promo = interclass.to_df( + administratif=False, + aggregat=aggregat, + tags_cibles=[tag], + cohorte="Promo", + options=self.options, + ) - if not df_promo.empty: - aff_aggregat += [aggregat + " (Promo)"] - df = df.join(df_promo) + if not df_promo.empty: + aff_aggregat += [aggregat + " (Promo)"] + colonnes_renommees = pe_moytag.ajout_numero_a_colonnes( + list(df.columns), list(df_groupe.columns) + ) + if colonnes_renommees: + df_promo.columns = colonnes_renommees + df = df.join(df_promo) if aff_aggregat: pe_affichage.pe_print( @@ -806,7 +820,7 @@ class JuryPE(object): "rang_groupe": "", "rang_promo": "", } - colonne = pe_moytag.get_colonne_df( + colonne = app.pe.moys.pe_moy.get_colonne_df( aggregat, tag, comp, "Groupe", "note" ) if colonne in df.columns: @@ -816,14 +830,14 @@ class JuryPE(object): est_significatif = True # else: # print(f"{colonne} manquante") - colonne = pe_moytag.get_colonne_df( + colonne = app.pe.moys.pe_moy.get_colonne_df( aggregat, tag, comp, "Groupe", "rang" ) if colonne in df.columns: valeur = df.loc[etudid, colonne] if valeur and str(valeur) != "nan": moy[comp]["rang_groupe"] = valeur - colonne = pe_moytag.get_colonne_df( + colonne = app.pe.moys.pe_moy.get_colonne_df( aggregat, tag, comp, "Promo", "rang" ) if colonne in df.columns: diff --git a/app/pe/rcss/pe_rcs.py b/app/pe/rcss/pe_rcs.py index c7737153..c1c6449a 100644 --- a/app/pe/rcss/pe_rcs.py +++ b/app/pe/rcss/pe_rcs.py @@ -92,8 +92,8 @@ class RCS: """Nom du RCS""" assert self.nom in TOUS_LES_RCS, "Le nom d'un RCS doit ĂȘtre un aggrĂ©gat" - self.aggregat: list[str] = TYPES_RCS[nom]["aggregat"] - """AggrĂ©gat (liste des nom des semestres aggrĂ©gĂ©s)""" + self.noms_semestres_aggreges: list[str] = TYPES_RCS[nom]["aggregat"] + """Noms des semestres aggrĂ©gĂ©s)""" self.formsemestre_final: FormSemestre = semestre_final """(Form)Semestre final du RCS""" diff --git a/tests/unit/test_pe.py b/tests/unit/test_pe.py index 73e3cf03..0d0f102e 100644 --- a/tests/unit/test_pe.py +++ b/tests/unit/test_pe.py @@ -4,6 +4,7 @@ Test calcul moyennes pour les poursuites d'Ă©tudes import numpy as np import pytest +import app.pe.moys.pe_moy from tests.unit import setup from app import db @@ -24,10 +25,17 @@ def egalite_df(df1, df2): class Test_pe_moy: + infos = { + "aggregat": "S1", + "tag": "maths", + "intitule": "UE1.1", + "cohorte": "groupe", + } + def test_init(self): """Test de pe_moy.Moyenne.__init__""" notes = pd.Series({1: 10.0, 2: 14.0, 3: np.nan, 4: 0.0}) - moy = pe_moy.Moyenne(notes) + moy = pe_moy.Moyenne(notes, Test_pe_moy.infos) assert moy.etudids == [1, 2, 3, 4], "Etudids incorrect" assert moy.inscrits_ids == [1, 2, 4], "Inscriptions incorrectes" # Les notes @@ -67,7 +75,7 @@ class Test_pe_moy: def test_init_ex_aequo(self): """Test de pe_moy.Moyenne.__init__ pour des ex-aequo""" notes = pd.Series({1: 10.0, 2: 14.0, 3: 10.0, 4: 0.0}) - moy = pe_moy.Moyenne(notes) + moy = pe_moy.Moyenne(notes, Test_pe_moy.infos) # Les rangs rang = pd.Series(["2 ex/4", "1/4", "2 ex/4", "3/4"], index=[1, 2, 3, 4]) assert moy.df["rang"].isnull().sum() == 0, "Des Nan dans les rangs interdits" @@ -84,16 +92,11 @@ class Test_pe_moy: ], ) def test_has_notes(self, notes, resultat): - moy = pe_moy.Moyenne(notes) + moy = pe_moy.Moyenne(notes, Test_pe_moy.infos) assert ( moy.has_notes() == resultat ), "Le test sur la prĂ©sence de notes est incorrect" - -# ****************************** -# app.pe.moys.pe_moytag -# ****************************** -class Test_pe_moytag: @pytest.mark.parametrize( "aggregat, tag, champ, cohorte, critere, attendu", [ @@ -121,9 +124,36 @@ class Test_pe_moytag: ], ) def test_colonnes_df(self, aggregat, tag, champ, cohorte, critere, attendu): - descr = pe_moytag.get_colonne_df(aggregat, tag, champ, cohorte, critere) + descr = app.pe.moys.pe_moy.get_colonne_df( + aggregat, tag, champ, cohorte, critere + ) assert descr == attendu, "Nom de colonne incorrect" + def to_df(self): + notes = pd.Series({1: 10.0, 2: 14.0, 3: np.nan, 4: 0.0}) + infos = { + "aggregat": "S1", + "tag": "maths", + "intitule": "GĂ©nĂ©ral", + "cohorte": "promo", + } + cols = [ + f"S1|maths|GĂ©nĂ©ral|promo|{critere}" + for critere in ["note", "rang", "min", "max", "moy"] + ] + moy = pe_moy.Moyenne(notes, infos) + df = moy.to_df(with_min_max_moy=True) + assert ( + list(df.columns) == cols + ), "Colonnes du df de synthĂšse pour Excel pas correctes" + + +# ****************************** +# app.pe.moys.pe_moytag +# ****************************** +class Test_pe_moytag: + infos = {"aggregat": "S1", "cohorte": "promo"} + def test_moyennes_tag__init__(self): matrice_notes = pd.DataFrame.from_dict( { @@ -149,7 +179,9 @@ class Test_pe_moytag: orient="index", columns=["UE1.1", "UE1.2", "UE1.3"], ) - moy_tag = pe_moytag.MoyennesTag("maths", None, matrice_notes, matrice_coeffs) + moy_tag = pe_moytag.MoyennesTag( + "maths", None, matrice_notes, matrice_coeffs, Test_pe_moytag.infos + ) attendu = pd.Series( [ (12 * 1 + 14 * 2 + 15 * 3) / (1 + 2 + 3), @@ -163,35 +195,9 @@ class Test_pe_moytag: ) assert egalite_df(moy_tag.notes_gen, attendu), "La moyenne n'est pas correcte" - def test_to_df(self): - matrice_notes = pd.DataFrame.from_dict( - { - 1: [12.0, 14.0, 15.0], - 2: [8.0, np.nan, 12.0], - 3: [0.0, 11.0, 13.0], - 4: [np.nan, np.nan, np.nan], - 5: [np.nan, np.nan, np.nan], - 6: [0.0, 0.0, 0.0], - }, - orient="index", - columns=["UE1.1", "UE1.2", "UE1.3"], - ) - matrice_coeffs = pd.DataFrame.from_dict( - { - 1: [1, 2, 3], - 2: [2, 10, 6], - 3: [1, 2, np.nan], - 4: [5, 4, 3], - 5: [np.nan, np.nan, np.nan], - 6: [1, 1, 1], - }, - orient="index", - columns=["UE1.1", "UE1.2", "UE1.3"], - ) - moy_tag = pe_moytag.MoyennesTag("maths", None, matrice_notes, matrice_coeffs) - - def test_to_df(self): + def test_to_df_sans_renommage_colonne(self): """Test le dataframe de synthĂšse""" + infos = {"aggregat": "S1", "cohorte": "promo"} matrice_notes = pd.DataFrame.from_dict( { 2: [13.0, 13.0, 13], @@ -208,14 +214,16 @@ class Test_pe_moytag: orient="index", columns=["UE1.1", "UE1.2", "UE1.3"], ) - moy_tag = pe_moytag.MoyennesTag("maths", None, matrice_notes, matrice_coeffs) + moy_tag = pe_moytag.MoyennesTag( + "maths", None, matrice_notes, matrice_coeffs, infos + ) synthese = moy_tag.to_df( - aggregat="S1", cohorte="groupe", options={"min_max_moy": True} + aggregat="S1", cohorte="promo", options={"min_max_moy": True} ) colonnes_attendues = [] for ue in ["UE1.1", "UE1.2", "UE1.3", "GĂ©nĂ©ral"]: for champ in ["note", "rang", "min", "max", "moy"]: - colonnes_attendues += [f"S1|maths|{ue}|groupe|{champ}"] + colonnes_attendues += [f"S1|maths|{ue}|promo|{champ}"] assert ( list(synthese.columns) == colonnes_attendues ), "Les colonnes de synthĂšse ne sont pas correctes" @@ -224,6 +232,26 @@ class Test_pe_moytag: 2, ], "Les lignes ne sont pas triĂ©es par id d'Ă©tudiants" + @pytest.mark.parametrize( + "colonnes, ajouts, resultat", + [ + pytest.param(["toto", "titi"], ["tutu"], None, id="pas de modif"), + pytest.param(["toto", "titi"], ["titi"], ["titi (1)"], id="ajout de (1)"), + pytest.param( + ["toto", "toto (1)"], ["toto"], ["toto (2)"], id="ajout de (2)" + ), + pytest.param( + ["toto (1)", "titi (3)"], + ["toto", "titi"], + ["toto (4)", "titi (4)"], + id="ajout multiple", + ), + ], + ) + def test_renomme_colonnes(self, colonnes, ajouts, resultat): + res = pe_moytag.ajout_numero_a_colonnes(colonnes, ajouts) + assert res == resultat, "L'ajout d'un numĂ©ro au colonne est incorrect" + # ****************************** # app.pe.moys.pe_rcstag