diff --git a/app/pe/pe_etudiant.py b/app/pe/pe_etudiant.py index f608976e..35e9f942 100644 --- a/app/pe/pe_etudiant.py +++ b/app/pe/pe_etudiant.py @@ -680,18 +680,3 @@ def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str: return " ".join(description) - -def convert_trajectoire_to_sxtag_id(trajectoire: dict[int:FormSemestre]) -> (int, int): - """Partant d'une trajectoire (dictionnaire de la forme {fid: FormSemestre}), - renvoie l'identifiant (rang_sem, fid du semestre_terminal) associé""" - if not trajectoire: - return None - rangs = [formsemestre.semestre_id for formsemestre in trajectoire.values()] - assert len(set(rangs)) == 1, "Les trajectoires doivent être de même rang" - rang = min(rangs) - fid_terminal = list(trajectoire.values())[0].formsemestre_id - for fid, formsemestre in trajectoire.items(): - if trajectoire[fid_terminal].date_fin <= formsemestre.date_fin: - fid_terminal = fid - return (rang, fid_terminal) - diff --git a/app/pe/pe_interclasstag.py b/app/pe/pe_interclasstag.py index 000feacf..b015136b 100644 --- a/app/pe/pe_interclasstag.py +++ b/app/pe/pe_interclasstag.py @@ -98,7 +98,7 @@ class RCSInterclasseTag(TableTag): """Association entre chaque étudiant et la trajectoire tagguée à prendre en compte pour l'aggrégat""" for etudid in self.diplomes_ids: - self.suivi[etudid] = rcss_jury_pe.suivi[etudid][nom_rcs] + self.suivi[etudid] = rcss_jury_pe.rcss_suivis[etudid][nom_rcs] self.tags_sorted = self.do_taglist() """Liste des tags (triés par ordre alphabétique)""" diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index c0736271..f61b601e 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -94,6 +94,9 @@ class JuryPE(object): self.etudiants.find_etudiants() self.diplomes_ids = self.etudiants.diplomes_ids + self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome) + """Les informations sur les regroupements de semestres""" + self.zipdata = io.BytesIO() with ZipFile(self.zipdata, "w") as zipfile: if not self.diplomes_ids: @@ -102,8 +105,10 @@ class JuryPE(object): self._gen_xls_diplomes(zipfile) self._gen_xls_ressembuttags(zipfile) + self._gen_rcss() self._gen_xls_sxtags(zipfile) - # self._gen_xls_rcss_tags(zipfile) + # self._gen_rcrcfs() + # self._gen_xls_rcss_tags(zipfile) # self._gen_xls_interclassements_rcss(zipfile) # self._gen_xls_synthese_jury_par_tag(zipfile) # self._gen_xls_synthese_par_etudiant(zipfile) @@ -173,51 +178,46 @@ class JuryPE(object): ) def _gen_rcss(self): - """Génère les RCS (attribut `rcss_jury`), combinaisons de semestres suivis par les étudiants au sens - d'un nom de RCS (par ex: 'S2' ou '3S'). + """Génère les RCS (attribut `rcss_jury`), combinaisons de semestres + suivis par les étudiants au sens d'un nom de RCS (par ex: 'S2' ou '3S'). """ pe_affichage.pe_print( "*** Génère les RCS (différentes combinaisons de semestres) des étudiants" ) - self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome) + self.rcss_jury.cree_rcss(self.etudiants) + self.rcss_jury.cree_rcfs(self.etudiants) def _gen_xls_sxtags(self, zipfile: ZipFile): """Génère les semestres taggués en s'appuyant sur les RCS de type Sx (pour identifier les redoublements impactant les semestres taggués). """ + # Génère les regroupements de semestres de type Sx + pe_affichage.pe_print("*** Génère les RCF (RCS de type Sx)***") + self.rcss_jury.cree_rcfs(self.etudiants) + # Génère les moyennes des RCS de type Sx pe_affichage.pe_print("*** Calcule les moyennes des SxTag") - # Les regroupements de Sx - self.regroupements = {} - for rang in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1): - self.regroupements[rang] = {} - for etudid in self.etudiants.etudiants_ids: - trajectoire = self.etudiants.trajectoires[etudid][f"S{rang}"] - if trajectoire: - sxtag_id = pe_etudiant.convert_trajectoire_to_sxtag_id(trajectoire) - if sxtag_id not in self.regroupements[rang]: - self.regroupements[rang][sxtag_id] = {} - else: - self.regroupements[rang][sxtag_id] |= trajectoire - - # Les SxTag + # Les SxTag (moyenne de Sx par UE) self.sxtags = {} - for rang in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1): - for sxtag_id in self.regroupements[rang]: - trajectoires = self.regroupements[rang][sxtag_id] - ressemstags = {fid: self.ressembuttags[fid] for fid in trajectoires} - self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, ressemstags) + for rcf_id, rcf in self.rcss_jury.rcfs.items(): + # SxTag traduisant le RCF + sxtag_id = rcf_id + # Les resultats des semestres taggués à prendre en compte dans le RCF + ressemstags = { + fid: self.ressembuttags[fid] for fid in rcf.semestres_aggreges + } + self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, ressemstags) # Intègre le bilan des semestres taggués au zip final output = io.BytesIO() with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated output, engine="openpyxl" ) as writer: - for sem_tag in self.sxtags.values(): - onglet = sem_tag.get_repr(verbose=False) - df = sem_tag.df_moyennes_et_classements() + for sxtag in self.sxtags.values(): + onglet = sxtag.get_repr(verbose=False) + df = sxtag.df_moyennes_et_classements() # écriture dans l'onglet df.to_excel(writer, onglet, index=True, header=True) output.seek(0) @@ -229,9 +229,16 @@ class JuryPE(object): path="details", ) - def _gen_xls_rcss_tags(self, zipfile: ZipFile): - """Génère les RCS taggués (autres que ceux de type Sx), etc... - en calculant les moyennes et les classements par tag pour chaque RCS. + def _gen_rcrcfs(self): + """Génère les regroupements cohérents de RCFs qu'ont suivi chaque étudiant""" + + pe_affichage.pe_print("*** Génère les RCRCF (regroupements de RCF de type Sx) amenant du S1 à un semestre final***") + self.rcss_jury.cree_rcrcfs(self.etudiants) + + + def _gen_xls_rcrcss_tags(self, zipfile: ZipFile): + """Génère les RCS taggués traduisant les moyennes (orientées compétences) + de regroupements de semestre de type Sx, xA ou xS. Stocke le résultat dans self.rccs_tag, un dictionnaire de la forme ``{nom_aggregat: {fid_terminal: RCSTag(fid_terminal)} }`` @@ -249,12 +256,11 @@ class JuryPE(object): """ # Génère les moyennes des RCS de type Sx - pe_affichage.pe_print("*** Calcule les moyennes des RCS") + pe_affichage.pe_print("*** Calcule les moyennes des RC de RCFS") self.rcss_tags = {} - for rcs_id, rcs in self.rcss_jury.rcss.items(): - # nom = rcs.get_repr() - self.rcss_tags[rcs_id] = RCSTag(rcs, self.res_sems_tags) + for rcs_id, rcrcf in self.rcss_jury.rcrcfs.items(): + self.rcss_tags[rcs_id] = RCSTag(rcrcf, self.sxtags) # Intègre le bilan des trajectoires tagguées au zip final output = io.BytesIO() @@ -431,7 +437,7 @@ class JuryPE(object): # considéré trajectoires_tagguees = [] for etudid in etudids: - trajectoire = self.rcss_jury.suivi[etudid][aggregat] + trajectoire = self.rcss_jury.rcss_suivis[etudid][aggregat] if trajectoire: tid = trajectoire.sxtag_id trajectoire_tagguee = self.rcss_tags[tid] @@ -577,7 +583,7 @@ class JuryPE(object): ) # La trajectoire de l'étudiant sur l'aggrégat - trajectoire = self.rcss_jury.suivi[etudid][aggregat] + trajectoire = self.rcss_jury.rcss_suivis[etudid][aggregat] if trajectoire: trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id] if tag in trajectoire_tagguee.moyennes_tags: diff --git a/app/pe/pe_moytag.py b/app/pe/pe_moytag.py index a2428f34..05d3882b 100644 --- a/app/pe/pe_moytag.py +++ b/app/pe/pe_moytag.py @@ -155,101 +155,74 @@ class Moyenne: return self.synthese["nb_inscrits"] > 0 + class MoyennesTag: def __init__( self, tag: str, - ues: dict[int, UniteEns], - notes_ues: pd.DataFrame, - ues_inscr_parcours_df: pd.DataFrame - # notes_gen: pd.Series, + matrice_notes: pd.DataFrame, # etudids x colonnes + matrice_coeffs: pd.DataFrame # etudids x colonnes ): """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) + Args: tag: Un tag - ues: La liste des UEs ayant servie au calcul de la moyenne - notes_ues: Les moyennes (etudid x acronymes_ues) aux différentes UEs et pour le tag - ues_inscr_parcours_df: Les inscriptions des etudid au UE + matrice_notes: Les moyennes (etudid x acronymes_ues ou etudid x compétences) aux différentes UEs ou compétences + matrice_coeffs: Les coeff à appliquer pour le calcul de la moyenne générale # notes_gen: Une série de notes (moyenne) sous forme d'un pd.Series() (toutes UEs confondues) """ self.tag = tag """Le tag associé aux moyennes""" - # Les UE - self.ues: dict[int, UniteEns] = ues - """Les UEs sur lesquelles sont calculées les moyennes""" - - colonnes = list(notes_ues.columns) - acronymes: list[str] = [self.ues[ue_id].acronyme for ue_id in self.ues] - assert len(set(acronymes)) == len( - colonnes - ), "Deux UEs ne peuvent pas avoir le même acronyme" - - # Les inscriptions des etudids aux UEs - self.ues_inscr_parcours_df: pd.DataFrame = ues_inscr_parcours_df - """Les inscriptions des etudids au UE en fonction de leur parcours""" - - # Les coefficients à appliquer aux UEs pour la moyenne générale = ECTS - self.ects = self.ues_inscr_parcours_df.fillna(0.0) * [ - ue.ects - for ue in self.ues.values() # if ue.type != UE_SPORT <= déjà supprimé - ] - # Les profils d'ects (pour debug) - profils_ects = [] - for val in list(self.ects.values): - if tuple(val) not in profils_ects: - profils_ects.append(tuple(val)) - # Les moyennes par UE - self.notes_ues: pd.DataFrame = notes_ues - """Les notes aux UEs (dataframe)""" - self.notes_ues.columns = acronymes # remplace les ue.id par leur acronyme - self.moys_ues: dict[int, pd.DataFrame] = {} + self.matrice_notes: pd.DataFrame = matrice_notes + """Les notes aux UEs ou aux compétences (DataFrame)""" + + self.matrice_coeffs: pd.DataFrame = matrice_coeffs + """Les coeffs à appliquer pour le calcul des moyennes générales + (toutes UE ou compétences confondues). NaN si étudiant non inscrit""" + + self.moyennes: dict[int, pd.DataFrame] = {} """Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs""" - for ue in self.ues.values(): # if ue.type != UE_SPORT: - notes = notes_ues[ue.acronyme] - self.moys_ues[ue.acronyme] = Moyenne(notes) + colonnes = self.matrice_notes.columns + for col in colonnes: # if ue.type != UE_SPORT: + notes = matrice_notes[col] + self.moyennes[col] = Moyenne(notes) # Les moyennes générales - notes_gen = self.compute_moy_gen(self.notes_ues, self.ects) + notes_gen = self.compute_moy_gen(self.matrice_notes, self.matrice_coeffs) self.notes_gen = notes_gen """Les notes générales (moyenne toutes UEs confonudes)""" - self.moy_gen = Moyenne(notes_gen) + self.moyenne_gen = Moyenne(notes_gen) """Le dataframe retraçant les moyennes/classements/statistiques général""" - pe_affichage.pe_print(f"> MoyTag pour {tag} avec") - pe_affichage.pe_print(f" - ues={acronymes}") - pe_affichage.pe_print(f" - ects={profils_ects}") - def __eq__(self, other): - """Egalité de deux MoyenneTag lorsque leur tag sont identiques""" - return self.tag == other.tag - - def compute_moy_gen( - self, moy_ues: pd.DataFrame, coeff_ues: pd.DataFrame - ) -> pd.Series: - """Calcule la moyenne générale (toutes UE confondus) + def compute_moy_gen(self, moys: pd.DataFrame, coeffs: pd.DataFrame) -> pd.Series: + """Calcule la moyenne générale (toutes UE/compétences confondus) pour le tag considéré, en pondérant les notes obtenues au UE - par les crédits ECTS. + par les coeff (généralement les crédits ECTS). Args: - moy_ues: Les moyennes etudids x acronymes_ues - coeff_ues: Les coeff etudids x ueids + moys: Les moyennes etudids x acronymes_ues/compétences + coeff: Les coeff etudids x ueids/compétences """ # Calcule la moyenne générale dans le semestre (pondérée par le ECTS) try: moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects( - moy_ues, - coeff_ues, + moys, + coeffs.fillna(0.0), # formation_id=self.formsemestre.formation_id, skip_empty_ues=True, ) except TypeError as e: - raise TypeError("Pb dans le calcul de la moyenne toutes UEs confondues") + raise TypeError( + "Pb dans le calcul de la moyenne toutes UEs/compétences confondues" + ) return moy_gen_tag + diff --git a/app/pe/pe_rcs.py b/app/pe/pe_rcs.py index 136109e4..ab245a05 100644 --- a/app/pe/pe_rcs.py +++ b/app/pe/pe_rcs.py @@ -12,7 +12,7 @@ Created on 01-2024 import app.pe.pe_comp as pe_comp from app.models import FormSemestre -from app.pe import pe_sxtag +from app.pe import pe_sxtag, pe_affichage from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date TYPES_RCS = { @@ -80,28 +80,14 @@ TOUS_LES_RCS = list(TYPES_RCS.keys()) TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")] +def get_descr_rcs(nom_rcs: str) -> str: + """Renvoie la description pour les tableurs de synthèse + Excel d'un nom de RCS""" + return TYPES_RCS[nom_rcs]["descr"] + + class RCS: - """Modélise un ensemble de semestres finals d'étudiants - associé à un type de regroupement cohérent de semestres - donné (par ex: 'S2', '3S', '2A'). - - Si le RCS est un semestre de type Si, stocke le - formsemestres de numéro i qu'ont suivi l'étudiant pour atteindre le Si - (en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants) - - Pour le RCS de type iS ou iA (par ex, 3A=S1+S2+S3), elle identifie - les semestres que les étudiants ont suivis pour les amener jusqu'au semestre - terminal de la trajectoire (par ex: ici un S3). - - Ces semestres peuvent être : - - * des S1+S2+S1+S2+S3 si redoublement de la 1ère année - * des S1+S2+(année de césure)+S3 si césure, ... - - Args: - nom_rcs: Un nom du RCS (par ex: '5S') - semestre_final: Le semestre final du RCS - """ + """Modélise un regroupement cohérent de semestres (formsemestre ou de Sx)""" def __init__(self, nom_rcs: str, semestre_final: FormSemestre): self.nom: str = nom_rcs @@ -110,42 +96,155 @@ class RCS: self.formsemestre_final: FormSemestre = semestre_final """FormSemestre terminal du RCS""" + self.rang_final = self.formsemestre_final.semestre_id + """Le rang du formsemestre final""" + self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id) """Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)""" - # self.semestres_aggreges: dict[int:FormSemestre] = {} - # """Semestres regroupés dans le RCS""" + def get_formsemestre_id_final(self) -> int: + """Renvoie l'identifiant du formsemestre final du RCS - self.sxtags_aggreges: dict[(str, int): pe_sxtag.SxTag] = {} - """Les SxTag aggrégés""" - - def get_formsemestre_id_final(self): - """Renvoie l'identifiant du formsemestre final du RCS""" + Returns: + L'id du formsemestre final (marquant la fin) du RCS + """ return self.formsemestre_final.formsemestre_id - def add_sxtags_a_aggreger(self, sxtags: dict[(str,int): pe_sxtag.SxTag]): - """Ajout des SxTag aux semestres à regrouper + def __repr__(self): + """Représentation textuelle d'un RCS""" + return f"{self.nom} ({self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}" + + def get_repr(self): + return self.__repr__() + + def __eq(self, other): + """Egalité de RCS""" + return ( + self.nom == other.nom + and self.formsemestre_final == other.formsemestre_final + ) + + +class RCF(RCS): + """Modélise un ensemble de (form)semestres d'étudiants + associé à un type de regroupement cohérent de semestres + donné (par ex: 'S2', '3S', '2A'). + + Si le RCF est un semestre de type Si, stocke les + formsemestres de numéro i qu'ont suivi l'étudiant pour atteindre le Si + (en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants) + + Pour le RCF de type iS ou iA (par ex, 3A=S1+S2+S3), identifie + les semestres que les étudiants ont suivis pour les amener jusqu'au semestre + terminal du RCS (par ex: ici un S3). + + Ces semestres peuvent être : + + * des S1+S2+S1+S2+S3 si redoublement de la 1ère année + * des S1+S2+(année de césure)+S3 si césure, ... + + Args: + nom_rcs: Un nom du RCS (par ex: '5S') + semestre_final: Le formsemestre final du RCS + """ + + def __init__(self, nom_rcs: str, semestre_final: FormSemestre): + RCS.__init__(self, nom_rcs, semestre_final) + + self.semestres_aggreges: dict[int:FormSemestre] = {} + """Formsemestres regroupés dans le RCS""" + + def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]): + """Ajout de semestres aux semestres à regrouper Args: - sxtags: Dictionnaire ``{(str,fid): SxTag}`` à ajouter + semestres: Dictionnaire ``{fid: Formsemestre)`` """ - self.sxtags_aggreges = self.sxtags_aggreges | sxtags + self.semestres_aggreges = self.semestres_aggreges | semestres def get_repr(self, verbose=True) -> str: """Représentation textuelle d'un RCS - basé sur ses sxtags aggrégés""" + basé sur ses semestres aggrégés""" noms = [] - for sxtag_id, sxtag in self.sxtags_aggreges.items(): - noms.append(f"S{sxtag.semestre_id}") + for fid in self.semestres_aggreges: + semestre = self.semestres_aggreges[fid] + noms.append(f"S{semestre.semestre_id}({fid})") noms = sorted(noms) - title = f"""{self.nom} ({ - self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}""" + title = f"""{str(self)}""" if verbose and noms: title += " - " + "+".join(noms) return title + +def get_rcf_from_semestres_aggreges( + rcfs: dict[(str, int):RCF], semestres_a_aggreges: list[FormSemestre] + ) -> (str, int): + """Partant d'un dictionnaire de RCFs (de la forme + ``{ (nom_rcs, fid): RCF }, et connaissant une liste + de (form)semestres à aggréger, renvoie l'identifiant + (nom_rcs, fid) du RCFs qui lui correspond (c'est à dire celui dont + les semestres_aggregés par le RCF sont les même que les + semestres_a_aggreger. + + Returns: + rcf_id: L'identifiant du RCF trouvé + """ + fids_semestres_a_aggreger = set( + [frms.formsemestre_id for frms in semestres_a_aggreges] + ) + for rcf_id, rcf in rcfs.items(): + fids_rcf = set(rcf.semestres_aggreges) + if fids_rcf == fids_semestres_a_aggreger: + return rcf_id + return None + + +class RCRCF: + """Modélise les RCF d'étudiants suivis par un étudiant dans + le cadre d'un RCS donné (par ex: 3S=S1+S2+S3). + + Pour rappel : un RCF (par ex. S1) combine les semestres 1 qu'a suivi + l'étudiant pour valider son S1 (1 si étudiant standard, 2 si redoublant). + + Le RCRCF 3S est donc le regroupement du RCF S1 + RCF S2 + RCF S3. + + Il est identifié par le formsemestre de S3 marquant la fin du regroupement. + + + Args: + nom_rcs: Un nom du RCS (par ex: '5S') + semestre_final: Le semestre final du RCS + """ + + def __init__(self, nom_rcs: str, semestre_final: FormSemestre): + RCS.__init__(self, nom_rcs, semestre_final) + + self.rcfs_aggreges: dict[(str, int) : pe_sxtag.SxTag] = {} + """Les RCFs à aggréger""" + + def add_rcfs_a_aggreger(self, rcfs: dict[(str, int):RCF]): + """Ajout des RCFs aux RCFS à regrouper + + Args: + rcfs: Dictionnaire ``{(str,fid): RCF}`` à ajouter + """ + self.rcfs_aggreges = self.rcfs_aggreges | rcfs + + def get_repr(self, verbose=True) -> str: + """Représentation textuelle d'un RCSF + basé sur ses RCF aggrégés""" + + noms = [] + for rcf_id, rcf in self.rcfs_aggreges.items(): + noms.append(rcf.get_repr()) + title = f"""{str(self)}""" + if verbose and noms: + title += " : " + "+".join(noms) + return title + + class RCSsJuryPE: """Classe centralisant tous les regroupements cohérents de semestres (RCS) des étudiants à prendre en compte dans un jury PE @@ -158,78 +257,151 @@ class RCSsJuryPE: self.annee_diplome = annee_diplome """Année de diplômation""" - self.rcss: dict[tuple:RCS] = {} - """Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCS}""" + self.rcss: dict[tuple(int, str) : RCF] = {} + """Ensemble des RCS recensés""" - self.suivi: dict[int:str] = {} + self.rcss_suivis: dict[int:dict] = {} """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, - son RCS : {etudid: {nom_RCS: RCS}}""" + son RCS : {etudid: {nom_RCS: RCS}}""" - def cree_rcss(self, etudiants: EtudiantsJuryPE, sxtags: dict[(str, int), pe_sxtag.SxTag]): + self.rcfs: dict[tuple(int, str) : RCF] = {} + """Ensemble des RCF recensés : {(nom_RCS, fid_terminal): RCF}""" + + self.rcfs_suivis: dict[int:dict] = {} + """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, + son RCS : {etudid: {nom_RCS: RCF}}""" + + self.rcrcfs: dict[tuple(int, str) : RCRCF] = {} + """Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCRCF}""" + + self.rcrcfs_suivis: dict[int:str] = {} + """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, + son RCRCF : {etudid: {nom_RCS: RCSx}}""" + + def cree_rcss(self, etudiants: EtudiantsJuryPE): """Créé tous les RCS, au regard du cursus des étudiants - analysés et des SxTag calculés. - - Les mémorise dans les données de chaque étudiant. + analysés + les mémorise dans les données de l'étudiant Args: etudiants: Les étudiants à prendre en compte dans le Jury PE - pe_sxtag: Les Sx taggués """ + for etudid in etudiants.trajectoires: + self.rcss_suivis[etudid] = { + aggregat: None + for aggregat in pe_comp.TOUS_LES_SEMESTRES + + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM + } + for nom_rcs in pe_comp.TOUS_LES_SEMESTRES + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM: # L'aggrégat considéré (par ex: 3S=S1+S2+S3), son nom de son semestre # terminal (par ex: S3) et son numéro (par ex: 3) - noms_semestre_de_aggregat = TYPES_RCS[nom_rcs]["aggregat"] # ["S1", "S2", "S3"] - nom_semestre_terminal = noms_semestre_de_aggregat[-1] # "S3" + noms_semestre_de_aggregat = TYPES_RCS[nom_rcs]["aggregat"] + nom_semestre_terminal = noms_semestre_de_aggregat[-1] for etudid in etudiants.trajectoires: - if etudid not in self.suivi: - self.suivi[etudid] = { - aggregat: None - for aggregat in pe_comp.TOUS_LES_SEMESTRES - + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM - } - # Le formsemestre terminal (dernier en date) associé au # semestre marquant la fin de l'aggrégat # (par ex: son dernier S3 en date) - semestres = etudiants.trajectoires[etudid][nom_semestre_terminal] - if semestres: - formsemestre_final = get_dernier_semestre_en_date(semestres) + trajectoire = etudiants.trajectoires[etudid][nom_semestre_terminal] + if trajectoire: + formsemestre_final = get_dernier_semestre_en_date(trajectoire) - # Ajout ou récupération de la trajectoire + # Ajout ou récupération du RCS associé rcs_id = (nom_rcs, formsemestre_final.formsemestre_id) if rcs_id not in self.rcss: - rcs = RCS(nom_rcs, formsemestre_final) - self.rcss[rcs_id] = rcs - else: - rcs = self.rcss[rcs_id] + self.rcss[rcs_id] = RCF(nom_rcs, formsemestre_final) + rcs = self.rcss[rcs_id] # La liste des semestres de l'étudiant à prendre en compte # pour cette trajectoire - semestres_a_aggreger = get_trajectoire_etudiant( + semestres_a_aggreger = get_rcs_etudiant( etudiants.trajectoires[etudid], formsemestre_final, nom_rcs ) - # Extrait les sxtags correspondants aux semestres à aggréger - # (par ex. des 2 semestres S1(18)+S1(26) récupère le sxtag S1(26) - sxtags_a_aggreger = {} - semestres_tries = pe_comp.tri_semestres_par_rang(semestres_a_aggreger) - for rang in semestres_tries: - sems = semestres_tries[rang] # les 1 ou 2 semestres de même rang suivi - sxtag_id = pe_sxtag.get_sxtag_from_semestres(sems, sxtags) - if not sxtag_id: - raise ValueError(f"Il manque un sxtag pour {sems}") - sxtags_a_aggreger[sxtag_id] = sxtags[sxtag_id] + # Ajout des semestres au RCS + rcs.add_semestres_a_aggreger(semestres_a_aggreger) - # Ajout des semestres à la trajectoire - rcs.add_sxtags_a_aggreger(sxtags_a_aggreger) + # Mémorise le RCS suivi par l'étudiant + self.rcss_suivis[etudid][nom_rcs] = rcs - # Mémoire la trajectoire suivie par l'étudiant - self.suivi[etudid][nom_rcs] = rcs + # Affichage pour debug + jeunes = list(enumerate(self.rcss_suivis)) + for no_etud, etudid in jeunes[:20]: + pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :") + for nom_rcs, rcs in self.rcss_suivis[etudid].items(): + if rcs: + pe_affichage.pe_print(f" > RCS {nom_rcs}: {rcs.get_repr()}") + + def cree_rcfs(self, etudiants: EtudiantsJuryPE): + """Créé les RCFs en ne conservant dans les RCS que les regroupements + de type Sx""" + self.rcfs = {} + for rcs_id, rcs in self.rcss.items(): + if rcs and rcs.nom in pe_comp.TOUS_LES_SEMESTRES: + self.rcfs[rcs_id] = rcs + print(self.rcfs) + + for etudid in self.rcss_suivis: + for nom_rcs, rcs in self.rcss_suivis[etudid].items(): + if rcs and nom_rcs in pe_comp.TOUS_LES_SEMESTRES: + if etudid not in self.rcfs_suivis: + self.rcfs_suivis[etudid] = {} + self.rcfs_suivis[etudid][nom_rcs] = rcs + + # Affichage pour debug + jeunes = list(enumerate(self.rcfs_suivis)) + for no_etud, etudid in jeunes[:20]: + pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :") + for nom_rcs, rcs in self.rcfs_suivis[etudid].items(): + if rcs: + pe_affichage.pe_print(f" > RCF {nom_rcs}: {rcs.get_repr()}") + else: + pe_affichage.pe_print(f" > RCF {nom_rcs}: !!! ") + + def cree_rcrcfs(self, etudiants: EtudiantsJuryPE): + """Créé tous les RCRCF, au regard du cursus des étudiants + analysés (trajectoires traduisant son parcours dans les + différents semestres) + les mémorise dans les données de l'étudiant + """ + + # Pour tous les étudiants du jury + for etudid in self.rcss_suivis: + self.rcrcfs_suivis[etudid] = {} + + for rcf_id, rcf in self.rcfs_suivis[etudid].items(): # Pour chaque RCS + semestres_a_aggreger = rcf.semestres_aggreges + + # Tri des semestres par rang + semestres_tries = pe_comp.tri_semestres_par_rang(semestres_a_aggreger) + + # Récupére les RCFs de type Sx traduisant sa trajectoire + rcfs_a_aggreger = {} + for semestres_du_rang in semestres_tries.values(): + if semestres_du_rang: + rcf_id = get_rcf_from_semestres_aggreges( + self.rcfs, semestres_du_rang + ) + if rcf_id: + raise ValueError( + "Il manque un RCF pour créer les RCRCFs dans cree_rcrcfs" + ) + rcfs_a_aggreger[rcf_id] = self.rcfs[rcf_id] + + # Ajout du RCRCF + if rcf_id not in self.rcrcfs: + self.rcrfs[rcf_id] = RCRCF(rcf_id, rcf.formsemestre_final) + rcrcf = self.rcrcfs[rcf_id] + + # Ajout des RCFs au RCRCF + rcrcf.add_rcfs_a_aggreger(rcfs_a_aggreger) + + # Mémoire la trajectoire RCRCF suivie par l'étudiant + nom_rcs = rcrcf.nom + self.rcrcfs_suivis[etudid][nom_rcs] = rcrcf -def get_trajectoire_etudiant( +def get_rcs_etudiant( semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str ) -> dict[int, FormSemestre]: """Ensemble des semestres parcourus (trajectoire) @@ -273,9 +445,3 @@ def get_trajectoire_etudiant( ): semestres_aggreges[fid] = semestre return semestres_aggreges - - -def get_descr_rcs(nom_rcs: str) -> str: - """Renvoie la description pour les tableurs de synthèse - Excel d'un nom de RCS""" - return TYPES_RCS[nom_rcs]["descr"] diff --git a/app/pe/pe_rcstag.py b/app/pe/pe_rcstag.py index 5af9ee04..9284ef6a 100644 --- a/app/pe/pe_rcstag.py +++ b/app/pe/pe_rcstag.py @@ -41,76 +41,81 @@ from app.pe import pe_affichage from app.pe.pe_ressemtag import ResSemBUTTag import pandas as pd import numpy as np -from app.pe.pe_rcs import RCS +from app.pe.pe_rcs import RCS, RCRCF +from app.pe.pe_sxtag import SxTag from app.pe.pe_tabletags import TableTag from app.pe.pe_moytag import MoyennesTag class RCSTag(TableTag): - - def __init__( - self, rcs: RCS, semestres_taggues: dict[int, ResSemBUTTag] - ): - """Calcule les moyennes par tag d'une combinaison de semestres - (RCS), pour extraire les classements par tag pour un + def __init__(self, rcrcf: RCS, sxstags: dict[(str, int): SxTag]): + """Calcule les moyennes par tag (orientées compétences) + d'un regroupement de SxTag + (RCRCF), 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. + participé au même semestre terminal. Args: rcs: Un RCS (identifié par un nom et l'id de son semestre terminal) - semestres_taggues: Les données sur les semestres taggués + sxstags: Les données sur les RCF taggués """ TableTag.__init__(self) - self.rcs_id: tuple(str, int) = rcs.rcs_id + self.rcs_id: tuple(str, int) = rcrcf.rcs_id """Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)""" - self.rcs: RCS = rcs - """RCS associé au RCS taggué""" + self.rcrcf: RCRCF = rcrcf + """RCRCF associé au RCS taggué""" self.nom = self.get_repr() """Représentation textuelle du RCS taggué""" - self.formsemestre_terminal = rcs.formsemestre_final + self.formsemestre_terminal = rcrcf.formsemestre_final """Le formsemestre terminal""" # Les résultats du formsemestre terminal nt = load_formsemestre_results(self.formsemestre_terminal) - self.semestres_aggreges = rcs.semestres_aggreges - """Les semestres aggrégés""" + self.rcfs_aggreges = rcrcf.rcfs_aggreges + """Les RCFs aggrégés""" - self.res_sems_tags = {} - """Les semestres tags associés aux semestres aggrégés""" + self.sxstags = {} + """Les SxTag associés aux RCF aggrégés""" try: - for frmsem_id in self.semestres_aggreges: - self.res_sems_tags[frmsem_id] = semestres_taggues[frmsem_id] + for rcf_id in self.rcfs_aggreges: + self.sxstags[rcf_id] = sxstags[rcf_id] except: - raise ValueError("Semestres taggués manquants") + raise ValueError("Semestres SxTag manquants") # Les étudiants (etuds, états civils & etudis) + self.etuds = nt.etuds self.add_etuds(nt.etuds) - # Les compétences (extraites des ues de tous les semestres) - self.ues = self.comp_ues(tag="but") + # Les compétences (extraites de tous les Sxtags) + self.competences_sorted = self.do_complist() + """Compétences extraites de tous les SxTag aggrégés""" # Les tags self.tags_sorted = self.do_taglist() - """Tags extraits de tous les semestres""" - - self.notes_cube = self.compute_notes_cube() - """Cube de notes""" - - etudids = list(self.etats_civils.keys()) - self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted) - """Calcul les moyennes par tag sous forme d'un dataframe""" + """Tags extraits de tous les SxTag aggrégés""" + # Les moyennes self.moyennes_tags: dict[str, MoyennesTag] = {} """Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)""" + for tag in self.tags_sorted: - moy_gen_tag = self.notes[tag] - self.moyennes_tags[tag] = MoyennesTag(tag, moy_gen_tag) + # Cube de note + notes_cube, coeffs_cube = self.compute_notes_comps_cube(tag) + + # Calcule des moyennes/coeffs sous forme d'un dataframe""" + moys_competences, coeffs_competences = compute_notes_competences( + notes_cube, coeffs_cube, self.etudids, self.competences_sorted + ) + + # Les moyennes + self.moyennes_tags[tag] = MoyennesTag(tag, moys_competences, + coeffs_competences) def __eq__(self, other): """Egalité de 2 RCS taggués sur la base de leur identifiant""" @@ -119,11 +124,14 @@ class RCSTag(TableTag): def get_repr(self, verbose=False) -> str: """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle est basée)""" - return self.rcs.get_repr(verbose=verbose) + return self.rcrcf.get_repr(verbose=verbose) - def compute_notes_cube(self): - """Construit le cube de notes (etudid x tags x semestre_aggregé) - nécessaire au calcul des moyennes de l'aggrégat + def compute_notes_comps_cube(self, tag): + """Pour un tag donné, construit : + * le cube de notes (etudid x competences x SxTag) nécessaire au calcul des moyennes, + en remplaçant les données d'UE (obtenus du SxTag) par les compétences + * le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions) + appliqué au calcul des différents SxTag """ # nb_tags = len(self.tags_sorted) # nb_etudiants = len(self.etuds) @@ -131,55 +139,90 @@ class RCSTag(TableTag): # Index du cube (etudids -> dim 0, tags -> dim 1) etudids = [etud.etudid for etud in self.etuds] - tags = self.tags_sorted - semestres_id = list(self.res_sems_tags.keys()) + competences_sorted = self.competences_sorted + sxstags_ids = list(self.sxstags.keys()) - dfs = {} + notes_dfs = {} + coeffs_dfs = {} - for frmsem_id in semestres_id: + for sxtag_id, sxtag in sxstags_ids.item(): # Partant d'un dataframe vierge - df = pd.DataFrame(np.nan, index=etudids, columns=tags) + notes_df = pd.DataFrame(np.nan, index=etudids, columns=competences_sorted) + coeffs_df = pd.DataFrame(np.nan, index=etudids, columns=competences_sorted) - # Charge les notes du semestre tag - notes = self.res_sems_tags[frmsem_id].notes + moys_tag = sxtag.moyennes_tags[tag] - # Les étudiants & les tags commun au dataframe final et aux notes du semestre) - etudids_communs = df.index.intersection(notes.index) - tags_communs = df.columns.intersection(notes.columns) + # Charge les notes et les coeffs du semestre tag + notes = moys_tag.matrice_notes.copy() # avec une copie + coeffs = moys_tag.matrice_coeffs.copy() # les coeffs - # Injecte les notes par tag - df.loc[etudids_communs, tags_communs] = notes.loc[ - etudids_communs, tags_communs + # Traduction des UE en compétences + association_ues_comp = moys_tag.competences + ues_columns_df = notes.columns + comp_associes_aux_ues = [association_ues_comp[ue] for ue in ues_columns_df] + notes.columns = comp_associes_aux_ues + coeffs.columns = comp_associes_aux_ues + + # Compétences communes + comp_communes = list(set(competences_sorted) & set(comp_associes_aux_ues)) + + # Etudiants communs + etudids_communs = notes_df.index.intersection(notes.index) + + # Recopie des notes et des coeffs + notes_df.loc[etudids_communs, comp_communes] = notes.loc[ + etudids_communs, comp_communes + ] + coeffs_df.loc[etudids_communs, comp_communes] = coeffs.loc[ + etudids_communs, comp_communes ] # Supprime tout ce qui n'est pas numérique - for col in df.columns: - df[col] = pd.to_numeric(df[col], errors="coerce") + for col in notes_df.columns: + notes_df[col] = pd.to_numeric(notes_df[col], errors="coerce") - # Stocke le df - dfs[frmsem_id] = df + # Stocke les dfs + notes_dfs[sxtag_id] = notes_df + coeffs_dfs[sxtag_id] = coeffs_df - """Réunit les notes sous forme d'un cube etdids x tags x semestres""" - semestres_x_etudids_x_tags = [dfs[fid].values for fid in dfs] - etudids_x_tags_x_semestres = np.stack(semestres_x_etudids_x_tags, axis=-1) + """Réunit les notes sous forme d'un cube etudids x competences x semestres""" + sxtag_x_etudids_x_comps = [notes_dfs[fid].values for fid in notes_dfs] + notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1) - return etudids_x_tags_x_semestres + """Réunit les coeffs sous forme d'un cube etudids x competences x semestres""" + sxtag_x_etudids_x_comps = [coeffs_dfs[fid].values for fid in notes_dfs] + coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1) + + return notes_etudids_x_comps_x_sxtag, coeffs_etudids_x_comps_x_sxtag def do_taglist(self): - """Synthétise les tags à partir des semestres (taggués) aggrégés + """Synthétise les tags à partir des Sxtags aggrégés Returns: Une liste de tags triés par ordre alphabétique """ tags = [] - for frmsem_id in self.res_sems_tags: - tags.extend(self.res_sems_tags[frmsem_id].tags_sorted) + for frmsem_id in self.sxstags: + tags.extend(self.sxstags[frmsem_id].tags_sorted) pe_affichage.pe_print(f"* Tags : {', '.join(tags)}") return sorted(set(tags)) + def do_complist(self): + """Synthétise les compétences à partir des Sxtags aggrégés""" + competences = [] + for sxtag_id, sxtag in self.sxstags: + comp = sxtag.moyennes_tags["but"].competences + competences.extend(comp) + return sorted(set(competences)) + + +def compute_notes_competences( + set_cube: np.array, coeff_cube: np.array, etudids: list, competences: list +): + """Calcule: + * la moyenne par compétences à un tag donné sur plusieurs semestres (partant du set_cube). + * la somme des coeffs à utiliser pour la moyenne générale. -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 *Remarque* : Adaptation de moy_ue.compute_ue_moys_apc au cas des moyennes de tag @@ -187,34 +230,41 @@ def compute_tag_moy(set_cube: np.array, etudids: list, tags: list): Args: set_cube: notes moyennes aux modules ndarray - (etuds x modimpls x UEs), des floats avec des NaN + (etuds x UEs|compétences x sxtags), des floats avec des NaN + coeffs_cube: somme des coeffs impliqués dans la moyennes etudids: liste des étudiants (dim. 0 du cube) + competences: list tags: liste des tags (dim. 1 du cube) Returns: Un DataFrame avec pour columns les moyennes par tags, et pour rows les etudid """ - nb_etuds, nb_tags, nb_semestres = set_cube.shape + nb_etuds, nb_comps, nb_semestres = set_cube.shape assert nb_etuds == len(etudids) - assert nb_tags == len(tags) + assert nb_comps == len(competences) # Quelles entrées du cube contiennent des notes ? mask = ~np.isnan(set_cube) - # Enlève les NaN du cube pour les entrées manquantes + # Enlève les NaN du cube de notes pour les entrées manquantes set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0) + coeffs_cube_no_nan = no.nan_to_num(coeff_cube, nan=0.0) # Les moyennes par tag with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2) + # La somme des coeffs + coeff_tag = np.sum(coeffs_cube_no_nan, axis=2) - # Le dataFrame + # Le dataFrame des notes moyennes etud_moy_tag_df = pd.DataFrame( etud_moy_tag, index=etudids, # les etudids - columns=tags, # les tags + columns=competences, # les competences ) - etud_moy_tag_df.fillna(np.nan) - return etud_moy_tag_df + coeffs_df = pd.DataFrame(coeff_tag, index=etudids, columns=competences) + coeffs_df.fillna(np.nan) + + return etud_moy_tag_df, coeffs_df diff --git a/app/pe/pe_ressemtag.py b/app/pe/pe_ressemtag.py index 8274e02d..0c4bc748 100644 --- a/app/pe/pe_ressemtag.py +++ b/app/pe/pe_ressemtag.py @@ -85,11 +85,25 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): ues_hors_sport = [ue for ue in self.ues if ue.type != UE_SPORT] self.ues_inscr_parcours_df = self.load_ues_inscr_parcours() + # Les compétences associées aux UEs + self.competences = {} + for ue in self.ues: + if ue.type != UE_SPORT: + assert ue.niveau_competence, ScoValueError("Des UEs ne sont pas rattachées à des compétences") + nom = ue.niveau_competence.competence.titre + self.competences[ue.ue_id] = nom + # Les tags personnalisés et auto: tags_dict = self._get_tags_dict() self._check_tags(tags_dict) # self.tags = [tag for cat in dict_tags for tag in dict_tags[cat]] + # Les coefficients + matrice_coeffs = self.ues_inscr_parcours_df * [ + ue.ects + for ue in ues_hors_sport # if ue.type != UE_SPORT <= déjà supprimé + ] + # Calcul des moyennes & les classements de chaque étudiant à chaque tag self.moyennes_tags = {} @@ -99,13 +113,10 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): moy_ues_tag = self.compute_moy_ues_tag(infos_tag) # moy_gen_tag = self.compute_moy_gen_tag(moy_ues_tag) - ues_dict = {ue.id: ue for ue in ues_hors_sport} self.moyennes_tags[tag] = MoyennesTag( tag, - ues_dict, moy_ues_tag, - self.ues_inscr_parcours_df - # moy_gen_tag + matrice_coeffs ) # Ajoute les d'UE moyennes générales de BUT pour le semestre considéré @@ -117,12 +128,8 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): {ue.id: self.etud_moy_ue[ue.id] for ue in ues_hors_sport}, index=self.etudids, ) - # moy_ues = self.nt.etud_moy_ue[ue_id] - # moy_gen_but = self.nt.etud_moy_gen - ues_dict = {ue.id: ue for ue in ues_hors_sport} - self.moyennes_tags["but"] = MoyennesTag( - "but", ues_dict, df_ues, self.ues_inscr_parcours_df # , moy_gen_but + "but", df_ues, matrice_coeffs # , moy_gen_but ) self.tags_sorted = self.get_all_tags() @@ -183,6 +190,13 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): self.dispense_ues, block=self.formsemestre.block_moyennes, ) + + # Transforme les UEs en acronyme + colonnes = moyennes_ues_tag.columns + ue_to_acro = {ue.id: ue.acronyme for ue in self.ues} + acronymes = [ue_to_acro[col] for col in colonnes] + moyennes_ues_tag.columns = acronymes + return moyennes_ues_tag def _get_tags_dict(self): @@ -299,27 +313,3 @@ def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre): return synthese_tags - -def get_noms_competences_from_ues(formsemestre: FormSemestre) -> dict[int, str]: - """Partant d'un formsemestre, extrait le nom des compétences associés - à (ou aux) parcours des étudiants du formsemestre. - - Ignore les UEs non associées à un niveau de compétence. - - Args: - formsemestre: Un FormSemestre - - Returns: - Dictionnaire {ue_id: nom_competence} lisant tous les noms des compétences - en les raccrochant à leur ue - """ - # Les résultats du semestre - nt = load_formsemestre_results(formsemestre) - - noms_competences = {} - for ue in nt.ues: - if ue.niveau_competence and ue.type != UE_SPORT: - # ?? inutilisé ordre = ue.niveau_competence.ordre - nom = ue.niveau_competence.competence.titre - noms_competences[ue.ue_id] = f"comp. {nom}" - return noms_competences diff --git a/app/pe/pe_sxtag.py b/app/pe/pe_sxtag.py index 71363028..00b41c07 100644 --- a/app/pe/pe_sxtag.py +++ b/app/pe/pe_sxtag.py @@ -50,7 +50,7 @@ from app.pe.pe_moytag import MoyennesTag class SxTag(TableTag): def __init__(self, sxtag_id: (int, int), ressembuttags: dict[int, ResSemBUTTag]): """Calcule les moyennes/classements par tag d'un semestre de type 'Sx' - (par ex. 'S1', 'S2', ...) : + (par ex. 'S1', 'S2', ...) avec une orientation par UE : * pour les étudiants non redoublants, ce sont les moyennes/classements du semestre suivi @@ -91,6 +91,7 @@ class SxTag(TableTag): self.etuds = ressembuttags[self.fid_final].etuds self.add_etuds(self.etuds) + # Affichage pe_affichage.pe_print(f"--> {self.get_repr()}") # Les tags @@ -99,38 +100,53 @@ class SxTag(TableTag): pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}") # Les UE - self.ues = self.ressembuttag_final.moyennes_tags["but"].ues + moy_sem_final = self.ressembuttag_final.moyennes_tags["but"] + self.ues = list(moy_sem_final.matrice_notes.columns) # Les acronymes des UE - self.acronymes_ues_sorted = sorted([ue.acronyme for ue in self.ues.values()]) + self.acronymes_ues_sorted = sorted(self.ues) # Les inscriptions des étudiants aux UEs # => ne conserve que les UEs du semestre final (pour les redoublants) - self.ues_inscr_parcours_df = self.ressembuttag_final.moyennes_tags[ + self.matrice_coeffs = self.ressembuttag_final.moyennes_tags[ "but" - ].ues_inscr_parcours_df + ].matrice_coeffs + self.ues_inscr_parcours = ~np.isnan(self.matrice_coeffs.to_numpy()) + # Les moyennes par tag self.moyennes_tags: dict[str, pd.DataFrame] = {} """Les notes aux UEs dans différents tags""" # Masque des inscriptions - inscr_mask = self.ues_inscr_parcours_df.to_numpy() + inscr_mask = self.ues_inscr_parcours for tag in self.tags_sorted: - # Cube de note + # Cube de note etudids x UEs notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues_sorted) # Calcule des moyennes sous forme d'un dataframe""" - moys_ues = compute_notes_ues( + matrice_moys_ues = compute_notes_ues( notes_cube, self.etudids, self.acronymes_ues_sorted, inscr_mask, ) + + # Les profils d'ects (pour debug) + profils_ects = [] + for i in self.matrice_coeffs.index: + val = tuple(self.matrice_coeffs.loc[i].fillna("x")) + if tuple(val) not in profils_ects: + profils_ects.append(tuple(val)) + # Les moyennes - self.moyennes_tags[tag] = MoyennesTag( - tag, self.ues, moys_ues, self.ues_inscr_parcours_df - ) + self.moyennes_tags[tag] = MoyennesTag(tag, + matrice_moys_ues, + self.matrice_coeffs) + + pe_affichage.pe_print(f"> MoyTag pour {tag} avec") + pe_affichage.pe_print(f" - ues={self.acronymes_ues_sorted}") + pe_affichage.pe_print(f" - ects={profils_ects}") def __eq__(self, other): """Egalité de 2 SxTag sur la base de leur identifiant""" @@ -140,7 +156,7 @@ class SxTag(TableTag): """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle est basée)""" affichage = [str(fid) for fid in self.ressembuttags] - return f"S{self.sxtag_id[0]}Tag ({'+'.join(affichage)})" + return f"{self.sxtag_id[0]}Tag ({'+'.join(affichage)})" def compute_notes_ues_cube(self, tag, acronymes_ues_sorted): """Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé) @@ -160,7 +176,7 @@ class SxTag(TableTag): # Charge les notes du semestre tag sem_tag = self.ressembuttags[frmsem_id] moys_tag = sem_tag.moyennes_tags[tag] - notes = moys_tag.notes_ues # dataframe etudids x ues + notes = moys_tag.matrice_notes # dataframe etudids x ues acronymes_ues_sem = list( notes.columns ) # les acronymes des UEs du semestre tag @@ -183,7 +199,7 @@ class SxTag(TableTag): # Stocke le df dfs[frmsem_id] = df - """Réunit les notes sous forme d'un cube semestres x etdids x ues""" + """Réunit les notes sous forme d'un cube etudids x ues x semestres""" semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs] etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1) return etudids_x_ues_x_semestres diff --git a/app/pe/pe_tabletags.py b/app/pe/pe_tabletags.py index 596aeae4..ea789ef5 100644 --- a/app/pe/pe_tabletags.py +++ b/app/pe/pe_tabletags.py @@ -94,13 +94,13 @@ class TableTag(object): tags_tries = self.get_all_tags() for tag in tags_tries: moy_tag = self.moyennes_tags[tag] - for acronyme in moy_tag.moys_ues: - moy = moy_tag.moys_ues[acronyme] # une moyenne + for acronyme in moy_tag.moyennes: + moy = moy_tag.moyennes[acronyme] # une moyenne df = df.join(moy.synthese["notes"].rename(f"Moy {tag}-{acronyme}")) df = df.join( moy.synthese["classements"].rename(f"Class {tag}-{acronyme}") ) - moy_gen = moy_tag.moy_gen + moy_gen = moy_tag.moyenne_gen df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag} (gen)")) df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag} (gen)"))