From 283daae4d9de21e57eddff46e41a43c3e302574d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9o=20BARAS=20=28IUT1=20Grenoble=29?= Date: Wed, 24 Jan 2024 15:37:50 +0100 Subject: [PATCH] =?UTF-8?q?Etat=20interm=C3=A9diaire=20n=C2=B04?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/pe/pe_etudiant.py | 245 ++++++------------ app/pe/pe_interclassetag.py | 124 +++++++++ app/pe/pe_jurype.py | 89 +++---- app/pe/pe_settag_interclasse.py | 2 +- app/pe/pe_trajectoire.py | 149 +++++++++++ app/pe/{pe_settag.py => pe_trajectoiretag.py} | 43 +-- 6 files changed, 420 insertions(+), 232 deletions(-) create mode 100644 app/pe/pe_interclassetag.py create mode 100644 app/pe/pe_trajectoire.py rename app/pe/{pe_settag.py => pe_trajectoiretag.py} (86%) diff --git a/app/pe/pe_etudiant.py b/app/pe/pe_etudiant.py index 880de957..1c82d238 100644 --- a/app/pe/pe_etudiant.py +++ b/app/pe/pe_etudiant.py @@ -39,15 +39,6 @@ Created on 17/01/2024 import app.pe.pe_tools as pe_tools from app.models import FormSemestre, Identite from app.pe.pe_tools import pe_print -from app.scodoc import ( - sco_etud, - codes_cursus, - sco_formsemestre, - sco_formsemestre_inscriptions, - sco_report, -) -import datetime - class EtudiantsJuryPE: """Classe centralisant la gestion des étudiants à prendre en compte dans un jury de PE""" @@ -63,19 +54,16 @@ class EtudiantsJuryPE: "Les identités des étudiants traités pour le jury" self.identites = {} # ex. ETUDINFO_DICT - "Les cursus (semestres suivis, abandons, dernier S1, S2, ...) des étudiants" + "Les cursus (semestres suivis, abandons) des étudiants" self.cursus = {} """Les aggrégats des semestres suivis (par ex: 3S=S1+S2+S3 à prendre en compte avec d'éventuels redoublements) des étudiants""" - self.aggregats = {} + self.trajectoires = {} "Les etudids des étudiants à considérer au jury (ceux qui seront effectivement diplômés)" self.diplomes_ids = {} "Les etudids des étudiants dont il faut calculer les moyennes/classements (même si d'éventuels abandons)" self.etudiants_ids = {} - "Les formsemestres dont il faut calculer les moyennes par tag" - self.formsemestres_jury_ids = {} - def find_etudiants(self, formation_id: int): """Liste des étudiants à prendre en compte dans le jury PE, en les recherchant de manière automatique par rapport à leur année de diplomation ``annee_diplome`` @@ -116,7 +104,7 @@ class EtudiantsJuryPE: self.analyse_etat_etudiant(etudid, cosemestres) """L'analyse de son parcours pour atteindre chaque semestre de la formation""" - self.analyse_parcours_etudiant_dans_semestres(etudid) + self.structure_cursus_etudiant(etudid) if (no_etud + 1) % 10 == 0: pe_tools.pe_print((no_etud + 1), " ", end="") @@ -130,7 +118,7 @@ class EtudiantsJuryPE: self.etudiants_ids = {etudid for etudid in self.identites} """Les formsemestres (des étudiants) dont il faut calculer les moyennes""" - self.formsemestres_jury_ids = self.get_formsemestres_jury() + self.formsemestres_jury_ids = self.get_formsemestres() # Synthèse pe_tools.pe_print( @@ -210,61 +198,37 @@ class EtudiantsJuryPE: """ Est-il réorienté / démissionnaire ou a-t-il arrêté volontairement sa formation ?""" self.cursus[etudid]["abandon"] = arret_de_formation(identite, cosemestres) - def analyse_parcours_etudiant_dans_semestres(self, etudid: int): - """Structure les informations sur les semestres suivis par un - étudiant, pour identifier les semestres qui seront pris en compte lors de ses calculs - de moyennes PE. + def get_semestres_significatifs(self, etudid: int): + """Ensemble des semestres d'un étudiant, qui l'auraient amené à être diplomé + l'année visée (supprime les semestres qui conduisent à une diplomation + postérieure à celle du jury visé) - Cette structuration (cf. attribut self.cursus) s'appuie sur les numéros de semestre: pour chaque Si, stocke : - le dernier semestre (en date) de numéro i qu'il a suivi (1 ou 0 si pas encore suivi). Ce semestre influera les - interclassement par semestre dans la promo. + Args: + etudid: L'identifiant d'un étudiant - Elle calcule également sur les aggrégats (cf. attribut self.aggregats): - - * pour un semestre de type Si, elle stocke le (ou les) formsemestres de numéro i qu'a suivi un étudiant - (2 si redoublant) dans self.aggregats - * pour des aggrégats de type iS ou iA (par ex, 3A=S1+S2+S3), elle identifie les semestres que l'étudiant - a suivi pour l'amener jusqu'au semestre terminal de l'aggrégat. Ce parcours peut être : - ** S1+S2+S1+S2+S3 si redoublement de la 1ère année - ** S1+S2+(année de césure)+S3 si césure, ... - - Par ex: M. N..z (redoublant en 2ème année) au moment de son 2ème S3 : - {'1A': {26: {18: , - 26: }}, - '2A': {79: {18: , - 26: , - 56: , - 79: }}, - '3A': {}, - '3S': {112: {18: , - 26: , - 56: , - 112: }}, - '4S': {79: {18: , - 26: , - 56: , - 79: }}, - '5S': {}, - '6S': {}, - 'S1': {18: {18: }}, - 'S2': {26: {26: }}, - 'S3': {112: {56: , - 112: }}, - 'S4': {79: {79: }}, - 'S5': {}, - 'S6': {} - } + Returns: + Un dictionnaire ``{fid: FormSemestre(fid)`` dans lequel les semestres + amènent à une diplomation avant l'annee de diplomation du jury """ - semestres_etudiant = self.cursus[etudid]["formsemestres"] - """Ne conserve que les semestres qui l'auraient amené à être diplomé l'année visée""" + semestres_etudiant = self.cursus[etudid]["formsemestres"] semestres_significatifs = {} for fid in semestres_etudiant: semestre = semestres_etudiant[fid] if pe_tools.get_annee_diplome_semestre(semestre) <= self.annee_diplome: semestres_significatifs[fid] = semestre + return semestres_significatifs - self.aggregats[etudid] = {} + def structure_cursus_etudiant(self, etudid: int): + """Structure les informations sur les semestres suivis par un + étudiant, pour identifier les semestres qui seront pris en compte lors de ses calculs + de moyennes PE. + + Cette structuration s'appuie sur les numéros de semestre: pour chaque Si, stocke : + le dernier semestre (en date) de numéro i qu'il a suivi (1 ou 0 si pas encore suivi). Ce semestre influera les + interclassement par semestre dans la promo. + """ + semestres_significatifs = self.get_semestres_significatifs(etudid) """Tri des semestres par numéro de semestre""" for nom_sem in pe_tools.TOUS_LES_SEMESTRES: @@ -274,49 +238,37 @@ class EtudiantsJuryPE: for fid in semestres_significatifs if semestres_significatifs[fid].semestre_id == i } # les semestres de n°i de l'étudiant - dernier_semestre_i = get_dernier_semestre(semestres_i) - self.cursus[etudid][nom_sem] = dernier_semestre_i - self.aggregats[etudid][nom_sem] = {} - if dernier_semestre_i: - fid_dernier_semestre_i = list(dernier_semestre_i.keys())[0] - self.aggregats[etudid][nom_sem][fid_dernier_semestre_i] = semestres_i + self.cursus[etudid][nom_sem] = semestres_i - """Tri des semestres par aggrégat et par semestre terminal""" - for aggregat in pe_tools.TOUS_LES_AGGREGATS: - self.aggregats[etudid][aggregat] = {} + def get_trajectoire(self, etudid: int, formsemestre_final: FormSemestre): + """Ensemble des semestres parcourus par + un étudiant pour l'amener à un semestre terminal. - """L'aggrégat considéré (par ex: 3S), son nom de son semestre terminal (par ex: S3) et son numéro (par ex: 3)""" - noms_semestre_de_aggregat = pe_tools.PARCOURS[aggregat]["aggregat"] - nom_semestre_terminal = noms_semestre_de_aggregat[-1] - numero_semestre_terminal = int(nom_semestre_terminal[-1]) + Par ex: si formsemestre_terminal est un S3, ensemble des S1, + S2, S3 suivi pour l'amener au S3 (il peut y avoir plusieurs S1, + ou S2, ou S3 s'il a redoublé). - """Le formsemestre terminal de l'aggrégat (par ex: son dernier S3 en date)""" - dernier_formsemestre_terminal = self.cursus[etudid][nom_semestre_terminal] + Les semestres parcourus sont antérieurs (en terme de date de fin) + au formsemestre_terminal. - # for formsem_id_term in formsemestres_terminal: - if dernier_formsemestre_terminal: # ne considérant que le dernier - formsem_id_term = list(dernier_formsemestre_terminal.keys())[0] + Args: + etudid: L'identifiant de l'étudiant + formsemestre_final: le semestre final visé + """ + numero_semestre_terminal = formsemestre_final.semestre_id + semestres_significatifs = self.get_semestres_significatifs(etudid) - formsemestre_terminal = self.cursus[etudid]["formsemestres"][ - formsem_id_term - ] - - """Semestres de n° inférieur (pax ex: des S1, S2, S3 pour un S3 terminal) et qui lui sont antérieurs""" - semestres_aggreges = {} - for fid in semestres_significatifs: - semestre = semestres_significatifs[fid] - if ( - semestre.semestre_id <= numero_semestre_terminal - and semestre.date_fin <= formsemestre_terminal.date_fin - ): - semestres_aggreges[fid] = semestre - - self.aggregats[etudid][aggregat][formsem_id_term] = semestres_aggreges - - """Vérifications""" - dernier_semestre_aggregat = get_dernier_semestre(semestres_aggreges) - assert dernier_semestre_aggregat == dernier_formsemestre_terminal + """Semestres de n° inférieur (pax ex: des S1, S2, S3 pour un S3 terminal) et qui lui sont antérieurs""" + semestres_aggreges = {} + for fid in semestres_significatifs: + semestre = semestres_significatifs[fid] + if ( + semestre.semestre_id <= numero_semestre_terminal + and semestre.date_fin <= formsemestre_final.date_fin + ): + semestres_aggreges[fid] = semestre + return semestres_aggreges def get_formsemestres_terminaux_aggregat(self, aggregat: str): """Pour un aggrégat donné, ensemble des formsemestres terminaux possibles pour l'aggrégat @@ -333,47 +285,25 @@ class EtudiantsJuryPE: aggregat: L'aggrégat Returns: - Un dictionnaire {fid: FormSemestre(fid)} + Un dictionnaire ``{fid: FormSemestre(fid)}`` """ formsemestres_terminaux = {} - for etudid in self.aggregats: - if self.aggregats[etudid][aggregat]: - print(self.aggregats[etudid][aggregat]) - """Le formsemestre_id du semestre terminal de l'étudiant (s'il existe)""" - fid = list(self.aggregats[etudid][aggregat].keys())[0] - """Le formsemestre associé (en le prenant dans l'aggrégat)""" - formsemestres_terminaux[fid] = self.aggregats[etudid][aggregat][fid][ - fid - ] + for etudid in self.trajectoires: + if self.trajectoires[etudid][aggregat]: + trajectoire = self.trajectoires[etudid][aggregat] + """Le semestre terminal de l'étudiant de l'aggrégat""" + fid = trajectoire.semestre_final.formsemestre_id + formsemestres_terminaux[fid] = trajectoire.semestre_final return formsemestres_terminaux - def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int): - """Pour un aggrégat donné associé à un formsemestre terminal cible, renvoie l'ensemble des semestres à - prendre en compte dans l'aggrégat sous la forme d'un dictionnaire {fid: FormSemestre(fid)}. - - Fusionne les cursus individuels des étudiants, dont le cursus correspond à l'aggrégat visé. - - Args: - aggregat: Un aggrégat (par ex. 1A, 2A, 3S, 6S) - formsemestre_id_terminal: L'identifiant du formsemestre terminal de l'aggrégat, devant correspondre au - dernier semestre de l'aggrégat - """ - noms_semestres_aggreges = pe_tools.PARCOURS[aggregat]["aggregat"] - - formsemestres = {} - for etudid in self.cursus: - cursus_etudiant = self.cursus[etudid][aggregat] - if formsemestre_id_terminal in cursus_etudiant: - formsemestres_etudiant = cursus_etudiant[formsemestre_id_terminal] - formsemestres = formsemestres | formsemestres_etudiant - return formsemestres - - def get_formsemestres_jury(self, semestres_recherches=None): + def get_formsemestres(self, semestres_recherches=None): """Ayant connaissance des étudiants dont il faut calculer les moyennes pour - le jury PE (attribut `self.etudiant_ids) et de leur cursus, - renvoie un dictionnaire `{fid: FormSemestre(fid)}` - contenant l'ensemble des formsemestres de leur cursus, dont il faudra calculer - la moyenne. Les formsemestres sont limités à ceux indiqués dans ``semestres_recherches``. + le jury PE (attribut `self.etudiant_ids) et de leur cursus (semestres + parcourus), + renvoie un dictionnaire ``{fid: FormSemestre(fid)}`` + contenant l'ensemble des formsemestres de leurs cursus, dont il faudra calculer + la moyenne. + Les formsemestres sont limités à ceux indiqués dans ``semestres_recherches``. Args: semestres_recherches: Une liste ou une chaine de caractères parmi : @@ -384,20 +314,20 @@ class EtudiantsJuryPE: * '3S', '4S' : pour obtenir les combinaisons de semestres définies par les aggrégats Returns: - Un dictionnaire de la forme {fid: FormSemestre(fid)} + Un dictionnaire de la forme ``{fid: FormSemestre(fid)}`` Remarque: - Une liste de la forme `[ 'Si', 'iA' , ... ]` (combinant les formats précédents) est possible. + Une liste de la forme ``[ 'Si', 'iA' , ... ]`` (combinant les formats précédents) est possible. """ if semestres_recherches is None: """Appel récursif pour obtenir tous les semestres (validants)""" - semestres = self.get_formsemestres_jury(pe_tools.AGGREGAT_DIPLOMANT) + semestres = self.get_formsemestres(pe_tools.AGGREGAT_DIPLOMANT) return semestres elif isinstance(semestres_recherches, list): """Appel récursif sur tous les éléments de la liste""" semestres = {} for elmt in semestres_recherches: - semestres_elmt = self.get_formsemestres_jury(elmt) + semestres_elmt = self.get_formsemestres(elmt) semestres = semestres | semestres_elmt return semestres elif ( @@ -405,7 +335,7 @@ class EtudiantsJuryPE: and semestres_recherches in pe_tools.TOUS_LES_AGGREGATS ): """Cas d'un aggrégat avec appel récursif sur toutes les entrées de l'aggrégat""" - semestres = self.get_formsemestres_jury( + semestres = self.get_formsemestres( pe_tools.PARCOURS[semestres_recherches]["aggregat"] ) return semestres @@ -420,24 +350,21 @@ class EtudiantsJuryPE: nom_sem = semestres_recherches semestres = {} for etudid in self.etudiants_ids: - for sem_terminal in self.aggregats[etudid]: - for sem in self.aggregats[etudid][sem_terminal]: - semestres = semestres | self.aggregats[etudid][sem_terminal][sem] + if self.cursus[etudid][nom_sem]: + semestres = semestres | self.cursus[etudid][nom_sem] return semestres else: - raise ValueError( - "Probleme de paramètres d'appel dans get_formsemestreids_du_jury" - ) + raise ValueError("Probleme de paramètres d'appel dans get_formsemestreids") -def get_etudiants_dans_semestres(semestres: dict[FormSemestre]) -> set: +def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set: """Ensemble d'identifiants des étudiants (identifiés via leur ``etudid``) inscrits à l'un des semestres de la liste de ``semestres``. Remarque : Les ``cosemestres`` sont généralement obtenus avec ``sco_formsemestre.do_formsemestre_list()`` Args: - semestres: Un dictionnaire {fid: Formsemestre(fid)} donnant un + semestres: Un dictionnaire ``{fid: Formsemestre(fid)}`` donnant un ensemble d'identifiant de semestres Returns: @@ -521,7 +448,8 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b etudid = identite.etudid """Son dernier semestre en date""" - dernier_formsemestre = identite.get_formsemestres()[0] + semestres = {sem.semestre_id: sem for sem in identite.get_formsemestres()} + dernier_formsemestre = get_dernier_semestre_en_date(semestres) numero_dernier_formsemestre = dernier_formsemestre.semestre_id """Les numéro de semestres possible dans lesquels il pourrait s'incrire""" @@ -555,27 +483,22 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b return False -def get_dernier_semestre(semestres: dict[int, FormSemestre]): - """Renvoie le dernier semestre en date (de fin) d'un dictionnaire - de semestres de la forme {fid: FormSemestre(fid)}. - La date prise en compte est celle marquant la **fin** des semestres. +def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]): + """Renvoie le dernier semestre en **date de fin** d'un dictionnaire + de semestres de la forme ``{fid: FormSemestre(fid)}``. Args: semestres: Un dictionnaire de semestres Return: - Un dictionnaire {fid: FormSemestre(fid)} contenant le semestre le plus récent + Le FormSemestre du semestre le plus récent """ if semestres: fid_dernier_semestre = list(semestres.keys())[0] - dernier_semestre = {fid_dernier_semestre: semestres[fid_dernier_semestre]} + dernier_semestre: FormSemestre = semestres[fid_dernier_semestre] for fid in semestres: - if ( - semestres[fid].date_fin - > dernier_semestre[fid_dernier_semestre].date_fin - ): - dernier_semestre = {fid: semestres[fid]} - fid_dernier_semestre = fid + if semestres[fid].date_fin > dernier_semestre.date_fin: + dernier_semestre = semestres[fid] return dernier_semestre else: - return {} + return None diff --git a/app/pe/pe_interclassetag.py b/app/pe/pe_interclassetag.py new file mode 100644 index 00000000..985837a6 --- /dev/null +++ b/app/pe/pe_interclassetag.py @@ -0,0 +1,124 @@ +import pandas as pd + +from app.pe import pe_tagtable +from app.pe.pe_tools import PE_DEBUG, pe_print +import app.pe.pe_etudiant as pe_etudiant +from app.pe.pe_etudiant import EtudiantsJuryPE +from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE +from app.comp import moy_sem + +import pandas as pd +import numpy as np + + +class AggregatInterclasseTag(pe_tagtable.TableTag): + """Interclasse l'ensemble des étudiants diplômés à une année + donnée (celle du jury), pour un aggrégat donné (par ex: 'S2', '3S') + en reportant : + + * les moyennes obtenues sur la trajectoire qu'il ont suivi pour atteindre le numéro de semestre de fin de l'aggrégat (indépendamment de son + formsemestres) + * calculant le classement sur les étudiants diplômes + """ + + # ------------------------------------------------------------------------------------------------------------------- + def __init__( + self, + nom_aggregat: str, + etudiants: EtudiantsJuryPE, + trajectoires_jury_pe: TrajectoiresJuryPE, + trajectoires_taggues: dict[tuple, TrajectoireTag], + ): + """""" + """Table nommée au nom de l'aggrégat (par ex: 3S""" + pe_tagtable.TableTag.__init__(self, nom_aggregat) + + """Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)""" + self.diplomes_ids = etudiants.diplomes_ids + self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids} + + """Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat + """ + self.trajectoires: dict[int, Trajectoire] = {} + for trajectoire_id in trajectoires_jury_pe.trajectoires: + trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id] + if trajectoire_id[0] == nom_aggregat: + self.trajectoires[trajectoire_id] = trajectoire + + self.trajectoires_taggues: dict[int, Trajectoire] = {} + for trajectoire_id in self.trajectoires: + self.trajectoires_taggues[trajectoire_id] = trajectoires_taggues[ + trajectoire_id + ] + + """Les trajectoires suivies par les étudiants du jury, en ne gardant que + celles associées aux diplomés""" + self.suivis: dict[int, Trajectoire] = {} + for etudid in self.diplomes_ids: + self.suivis[etudid] = trajectoires_jury_pe.suivis[etudid][nom_aggregat] + + """Les tags""" + self.tags_sorted = self.do_taglist() + + """Construit la matrice de notes""" + self.notes = self.compute_notes_matrice() + + """Synthétise les moyennes/classements par tag""" + self.moyennes_tags = {} + for tag in self.tags_sorted: + moy_gen_tag = self.notes[tag] + class_gen_tag = moy_sem.comp_ranks_series(moy_gen_tag)[1] # en int + self.moyennes_tags[tag] = { + "notes": moy_gen_tag, + "classements": class_gen_tag, + "min": moy_gen_tag.min(), + "max": moy_gen_tag.max(), + "moy": moy_gen_tag.mean(), + "nb_inscrits": len(moy_gen_tag), + } + + def do_taglist(self): + """Synthétise les tags à partir des trajectoires_tagguées + + Returns: + Une liste de tags triés par ordre alphabétique + """ + tags = [] + for trajectoire_id in self.trajectoires_taggues: + trajectoire = self.trajectoires_taggues[trajectoire_id] + tags.extend(trajectoire.tags_sorted) + return sorted(set(tags)) + + def get_etudids(self): + return list(self.etudiants_diplomes.keys()) + + def compute_notes_matrice(self): + """Construit la matrice de notes (etudid x tags) + retraçant les moyennes obtenues par les étudiants dans les semestres associés à + l'aggrégat (une trajectoire ayant pour numéro de semestre final, celui de l'aggrégat). + """ + nb_tags = len(self.tags_sorted) + nb_etudiants = len(self.diplomes_ids) + + """Index de la matrice (etudids -> dim 0, tags -> dim 1)""" + etudids = [etud.etudid for etud in self.diplomes_ids] + tags = self.tags_sorted + + """Partant d'un dataframe vierge""" + df = pd.DataFrame(np.nan, index=etudids, columns=tags) + + for trajectoire_id in self.trajectoires_taggues: + """Charge les moyennes par tag de la trajectoire tagguée""" + notes = self.trajectoires_taggues[trajectoire_id].notes + + """Etudiants/Tags communs entre la trajectoire_tagguée et les données interclassées""" + etudids_communs = df.index.intersection(notes.index) + tags_communs = df.columns.intersection(notes.columns) + + """Injecte les notes par tag""" + df.loc[etudids_communs, tags_communs] = notes.loc[ + etudids_communs, tags_communs + ] + + return df + diff --git a/app/pe/pe_jurype.py b/app/pe/pe_jurype.py index 0d183165..5b36638f 100644 --- a/app/pe/pe_jurype.py +++ b/app/pe/pe_jurype.py @@ -68,8 +68,9 @@ from app.scodoc import sco_formsemestre from app.pe import pe_tagtable from app.pe import pe_tools from app.pe import pe_semestretag -from app.pe import pe_settag +from app.pe import pe_trajectoiretag from app.pe.pe_etudiant import EtudiantsJuryPE +from app.pe.pe_trajectoire import TrajectoiresJuryPE # ---------------------------------------------------------------------------------------- @@ -154,17 +155,24 @@ class JuryPE(object): filename, formsemestretag.str_tagtable(), path="details_semestres" ) + """Génère les trajectoires (combinaison de semestres suivis + par un étudiant pour atteindre le semestre final d'un aggrégat) + """ + self.trajectoires = TrajectoiresJuryPE(self.diplome) + self.trajectoires.cree_trajectoires(self.etudiants) + + """Génère les aggrégats de semestre (par ex: 1A, 3S, 5S) avec calcul des moyennes pour le jury""" - self.aggregats_taggues = compute_aggregats_tag( + self.trajectoires_tagguees = compute_trajectoires_tag( self.etudiants, self.semestres_taggues ) if pe_tools.PE_DEBUG: """Intègre le bilan des aggrégats de semestres au zip final""" - for aggregat in self.aggregats_taggues: - for fid in self.aggregats_taggues[aggregat]: - set_tag = self.aggregats_taggues[aggregat][fid] + for aggregat in self.trajectoires_tagguees: + for fid in self.trajectoires_tagguees[aggregat]: + set_tag = self.trajectoires_tagguees[aggregat][fid] filename = set_tag.nom.replace(" ", "_") + ".csv" pe_tools.pe_print(f" - Export csv de {filename} ") self.add_file_to_zip( @@ -174,7 +182,7 @@ class JuryPE(object): """Génère les interclassements par (nom d') aggrégat""" self.aggregats_taggues_interclasses = compute_interclassements( self.etudiants, # - self.aggregats_taggues, + self.trajectoires_tagguees, ) # Les interclassements # -------------------- @@ -276,7 +284,7 @@ class JuryPE(object): self.etudiants.cursus[etudid][nom] ] else: - tagtable = self.aggregats_taggues[nom][ + tagtable = self.trajectoires_tagguees[nom][ self.etudiants.cursus[etudid][nom] ] for tag in tagtable.get_all_tags(): @@ -630,7 +638,7 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE): """Création des semestres taggués, de type 'S1', 'S2', ...""" pe_tools.pe_print("*** Création des semestres taggués") - formsemestres = etudiants.get_formsemestres_jury( + formsemestres = etudiants.get_formsemestres( semestres_recherches=pe_tools.TOUS_LES_SEMESTRES ) @@ -655,14 +663,18 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE): return semestres_tags -def compute_aggregats_tag(etudiants: EtudiantsJuryPE, semestres_tag: dict[SemestreTag]): - """Créé les combinaisons de semestres (aggrégats), en calculant les moyennes et les - classements par tag pour chacune. Chaque combinaison (aggrégat) est identifiée - par un formsemestre terminal. +def compute_trajectoires_tag(trajectoires: TrajectoiresJuryPE, + etudiants: EtudiantsJuryPE, + semestres_taggues: dict[int, SemestreTag]): + """Créée les trajectoires tagguées (combinaison aggrégeant plusieurs semestres au sens + d'un aggrégat (par ex: '3S')), + en calculant les moyennes et les classements par tag pour chacune. + + Pour rappel : Chaque trajectoire est identifiée un nom d'aggrégat et par un formsemestre terminal. Par exemple : - * combinaisons '3S' : S1+S2+S3 en prenant en compte tous les S3 qu'ont fréquentés les + * combinaisons '3S' : S1+S2+S3 en prenant en compte tous les S3 qu'ont fréquenté les étudiants du jury PE. Ces S3 marquent les formsemestre terminal de chaque combinaison. * combinaisons 'S2' : 1 seul S2 pour des étudiants n'ayant pas redoublé, 2 pour des redoublants (dont les @@ -680,53 +692,24 @@ def compute_aggregats_tag(etudiants: EtudiantsJuryPE, semestres_tag: dict[Semest pe_tools.pe_print(" *** Création des aggrégats ") - sets_tags = {} + trajectoires_tagguees = {} - for aggregat in pe_tools.TOUS_LES_SEMESTRES + pe_tools.TOUS_LES_AGGREGATS: - sets_tags[aggregat] = {} + for trajectoire_id in trajectoires_tagguees: + trajectoire = trajectoires[trajectoire_id] + nom = trajectoire.get_repr() - """Semestres aggrégés""" - if aggregat in pe_tools.TOUS_LES_SEMESTRES: # par ex. 'S2' - noms_semestres_aggreges = [aggregat] - else: # par ex. "5S" - noms_semestres_aggreges = pe_tools.PARCOURS[aggregat]["aggregat"] - nom_semestre_terminal = noms_semestres_aggreges[-1] + pe_tools.pe_print(f" --> Fusion {nom}") - pe_tools.pe_print(f"* {aggregat}: " + "+".join(noms_semestres_aggreges)) - - """Les formsemestres terminaux des aggrégats""" - formsemestres_terminal = etudiants.get_formsemestres_terminaux_aggregat( - aggregat - ) - - for frmsem_id in formsemestres_terminal: - formsemestre_terminal = formsemestres_terminal[frmsem_id] - """Nom du set_tag""" - nom = "Aggrégat %s %d %s %d-%d" % ( - aggregat, - frmsem_id, - "+".join(noms_semestres_aggreges), - formsemestre_terminal.date_debut.year, - formsemestre_terminal.date_fin.year, + """Création de la trajectoire_tagguee associée""" + trajectoire_tagguee = pe_trajectoiretag.TrajectoireTag( + nom, trajectoire, semestres_taggues, etudiants ) - """Semestres à aggreger dans l'aggrégat ayant amené des étudiants jusqu'au formsemestre_terminal""" - semestres_aggreges = etudiants.get_semestres_a_aggreger(aggregat, frmsem_id) + """Mémorise le résultat""" + trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee - pe_tools.pe_print(" --> Fusion de :") - for fid in semestres_aggreges: - pe_tools.pe_print(str(semestres_aggreges[fid])) + return trajectoires_tagguees - """Création du settag associé""" - settag = pe_settag.SetTag( - nom, formsemestre_terminal, semestres_aggreges, semestres_tag, etudiants - ) - - settag.compute_notes_cube() # Calcul les moyennes, les rangs, .. - - sets_tags[aggregat][fid] = settag # Mémorise le résultat - - return sets_tags def compute_interclassements( diff --git a/app/pe/pe_settag_interclasse.py b/app/pe/pe_settag_interclasse.py index 933383c3..83abe82d 100644 --- a/app/pe/pe_settag_interclasse.py +++ b/app/pe/pe_settag_interclasse.py @@ -1,7 +1,7 @@ from app.pe import pe_tagtable from app.pe.pe_tools import PE_DEBUG, pe_print import app.pe.pe_etudiant as pe_etudiant - +from app.pe.pe_etudiant import EtudiantsJuryPE class SetTagInterClasse(pe_tagtable.TableTag): """Interclasse les étudiants d'une promo (ceux diplômé) par aggrégat de même nom diff --git a/app/pe/pe_trajectoire.py b/app/pe/pe_trajectoire.py new file mode 100644 index 00000000..fbbf1ca8 --- /dev/null +++ b/app/pe/pe_trajectoire.py @@ -0,0 +1,149 @@ +import app.pe.pe_tools as pe_tools +from app.models import FormSemestre +from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date + + +class Trajectoire: + """Modélise, pour un aggrégat visé (par ex: 'S2', '3S', '2A') + et un ensemble d'étudiants donnés, + la combinaison des formsemestres des étudiants amenant à un semestre + terminal visé. + + Si l'aggrégat est un semestre de type Si, elle stocke le (ou les) + formsemestres de numéro i qu'ont suivis 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 des aggrégats 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, ... + """ + + def __init__(self, nom_aggregat: str, semestre_final: FormSemestre): + """Modélise un ensemble de formsemestres d'étudiants + amenant à un semestre terminal + + Args: + nom_aggregat: Un nom d'aggrégat (par ex: '5S') + semestre_final: Le semestre final de l'aggrégat + """ + self.nom = nom_aggregat + self.semestre_final = semestre_final + self.trajectoire_id = (nom_aggregat, semestre_final.formsemestre_id) + + """Les semestres à aggréger""" + self.semestres_aggreges = {} + + + def add_semestres_a_aggreger(self, semestres: dict[int: FormSemestre]): + """Ajoute des semestres au semestre à aggréger + + Args: + semestres: Dictionnaire ``{fid: FormSemestre(fid)} à ajouter`` + """ + self.semestres_aggreges = self.semestres_aggreges | semestres + + + + def get_repr(self): + """Représentation textuelle d'une trajectoire + basée sur ses semestres aggrégés""" + noms = [] + for fid in self.semestres_aggreges: + semestre = self.semestres_aggreges[fid] + noms.append(f"S{semestre.semestre_id}({fid})") + noms = sorted(noms) + repr = f"{self.nom} ({self.semestre_final.formsemestre_id}) {self.semestre_final.date_fin.year}" + if noms: + repr += " - " + "+".join(noms) + return repr + + +class TrajectoiresJuryPE: + """Centralise toutes les trajectoires du jury PE""" + + def __init__(self, annee_diplome: int): + """ + Args: + annee_diplome: L'année de diplomation + """ + + self.annee_diplome = annee_diplome + """Toutes les trajectoires possibles""" + self.trajectoires: dict[tuple: Trajectoire] = {} + """Quelle trajectoires pour quel étudiant : + dictionnaire {etudid: {nom_aggregat: Trajectoire}}""" + self.suivi: dict[int: str] = {} + + + def cree_trajectoires(self, etudiants: EtudiantsJuryPE): + """Créé toutes les trajectoires, au regard du cursus des étudiants + analysés + les mémorise dans les données de l'étudiant + """ + + for nom_aggregat in pe_tools.TOUS_LES_SEMESTRES + pe_tools.TOUS_LES_AGGREGATS: + + """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 = pe_tools.PARCOURS[nom_aggregat]["aggregat"] + nom_semestre_terminal = noms_semestre_de_aggregat[-1] + + for etudid in etudiants.cursus: + if etudid not in self.suivi: + self.suivi[etudid] = {} + + """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.cursus[etudid][nom_semestre_terminal] + if semestres: + formsemestre_final = get_dernier_semestre_en_date(semestres) + + """Ajout ou récupération de la trajectoire""" + trajectoire_id = (nom_aggregat, formsemestre_final.formsemestre_id) + if trajectoire_id not in self.trajectoires: + trajectoire = Trajectoire(nom_aggregat, formsemestre_final) + self.trajectoires[trajectoire_id] = trajectoire + else: + trajectoire = self.trajectoires[trajectoire_id] + + """La liste des semestres de l'étudiant à prendre en compte + pour cette trajectoire""" + semestres_a_aggreger = etudiants.get_trajectoire(etudid, formsemestre_final) + + """Ajout des semestres à la trajectoire""" + trajectoire.add_semestres_a_aggreger(semestres_a_aggreger) + + """Mémoire la trajectoire suivie par l'étudiant""" + self.suivi[etudid][nom_aggregat] = trajectoire + + """Vérifications""" + # dernier_semestre_aggregat = get_dernier_semestre_en_date(semestres_aggreges) + # assert dernier_semestre_aggregat == formsemestre_terminal + + +def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int): + """Pour un nom d'aggrégat donné (par ex: 'S3') et un semestre terminal cible + identifié par son formsemestre_id (par ex: 'S3 2022-2023'), + renvoie l'ensemble des semestres à prendre en compte dans + l'aggrégat sous la forme d'un dictionnaire {fid: FormSemestre(fid)}. + + Fusionne les cursus individuels des étudiants, dont le cursus correspond + à l'aggrégat visé. + + Args: + aggregat: Un aggrégat (par ex. 1A, 2A, 3S, 6S) + formsemestre_id_terminal: L'identifiant du formsemestre terminal de l'aggrégat, devant correspondre au + dernier semestre de l'aggrégat + """ + noms_semestres_aggreges = pe_tools.PARCOURS[aggregat]["aggregat"] + + formsemestres = {} + for etudid in self.cursus: + cursus_etudiant = self.cursus[etudid][aggregat] + if formsemestre_id_terminal in cursus_etudiant: + formsemestres_etudiant = cursus_etudiant[formsemestre_id_terminal] + formsemestres = formsemestres | formsemestres_etudiant + return formsemestres + diff --git a/app/pe/pe_settag.py b/app/pe/pe_trajectoiretag.py similarity index 86% rename from app/pe/pe_settag.py rename to app/pe/pe_trajectoiretag.py index 2474d0b9..547599e4 100644 --- a/app/pe/pe_settag.py +++ b/app/pe/pe_trajectoiretag.py @@ -35,6 +35,7 @@ Created on Fri Sep 9 09:15:05 2016 @author: barasc """ + from app.comp import moy_sem from app.comp.res_sem import load_formsemestre_results from app.models import FormSemestre @@ -42,35 +43,43 @@ from app.pe.pe_semestretag import SemestreTag from app.pe import pe_tagtable import pandas as pd import numpy as np - +from app.pe.pe_trajectoire import Trajectoire from app.pe.pe_etudiant import EtudiantsJuryPE +from app.pe.pe_trajectoire import TrajectoiresJuryPE -class SetTag(pe_tagtable.TableTag): - """Agrège plusieurs semestres (ou settag) taggués (SemestreTag/Settag de 1 à 4) pour extraire des moyennes - et des classements par tag pour un groupe d'étudiants donnés. - par. exemple fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S' - Le settag est identifié sur la base du dernier semestre (ici le 'S3') ; - les étudiants considérés sont donc ceux inscrits dans ce S3 - à condition qu'ils disposent d'un parcours sur tous les semestres fusionnés valides (par. ex - un etudiant non inscrit dans un S1 mais dans un S2 et un S3 n'est pas pris en compte). +class TrajectoireTag(pe_tagtable.TableTag): + """Calcule les moyennes par tag d'une combinaison de semestres + (trajectoires), identifiée par un nom d'aggrégat (par ex: '3S') et + par un semestre terminal, pour extraire les classements par tag pour un + groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous + participé au semestre terminal. + + Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S' + """ + # ------------------------------------------------------------------------------------------------------------------- def __init__( self, - nom, - formsemestre_terminal: FormSemestre, - semestres_aggreges: dict[int, FormSemestre], + nom: str, + trajectoire: Trajectoire, semestres_taggues: dict[int, SemestreTag], donnees_etudiants: EtudiantsJuryPE, ): - pe_tagtable.TableTag.__init__(self, nom) + """ """ + pe_tagtable.TableTag.__init__(self, nom=nom) + + """La trajectoire associée""" + self.trajectoire_id = trajectoire.trajectoire_id + self.trajectoire = trajectoire """Le formsemestre terminal et les semestres aggrégés""" - self.formsemestre_terminal = formsemestre_terminal - nt = load_formsemestre_results(formsemestre_terminal) - self.semestres_aggreges = semestres_aggreges + self.formsemestre_terminal = trajectoire.semestre_final + nt = load_formsemestre_results(self.formsemestre_terminal) + + self.semestres_aggreges = trajectoire.semestres_aggreges """Les semestres tags associés aux semestres aggrégés""" try: @@ -83,6 +92,7 @@ class SetTag(pe_tagtable.TableTag): """Les étudiants (état civil + cursus connu)""" self.etuds = nt.etuds + # assert self.etuds == trajectoire.suivi # manque-t-il des étudiants ? self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds} self.cursus = { etudid: donnees_etudiants.cursus[etudid] for etudid in self.etudiants @@ -155,7 +165,6 @@ class SetTag(pe_tagtable.TableTag): def get_etudids(self): return list(self.etudiants.keys()) - # ------------------------------------------------------------------------------------------------------------------- def do_taglist(self): """Synthétise les tags à partir des semestres (taggués) aggrégés