From 9c4e2627baf3ac1b9e83783a90e5a8cdec21a940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9o=20BARAS=20=28IUT1=20Grenoble=29?= Date: Mon, 5 Feb 2024 12:58:09 +0100 Subject: [PATCH] =?UTF-8?q?Am=C3=A9lioration=20nomenclature=20:=20trajecto?= =?UTF-8?q?ire=20=3D>=20RCS=20+=20m=C3=A9nage=20et=20refonte=20codes=20(?= =?UTF-8?q?=C3=A9tat=20interm=C3=A9diaire=20n=C2=B01)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/pe/pe_comp.py | 59 +-------- app/pe/pe_etudiant.py | 122 ++--------------- app/pe/pe_interclasstag.py | 18 +-- app/pe/pe_jury.py | 78 ++++++----- app/pe/pe_rcs.py | 253 ++++++++++++++++++++++++++++++++++++ app/pe/pe_trajectoire.py | 145 --------------------- app/pe/pe_trajectoiretag.py | 33 +++-- 7 files changed, 337 insertions(+), 371 deletions(-) create mode 100644 app/pe/pe_rcs.py delete mode 100644 app/pe/pe_trajectoire.py diff --git a/app/pe/pe_comp.py b/app/pe/pe_comp.py index 8b1f8985..a4e2548d 100644 --- a/app/pe/pe_comp.py +++ b/app/pe/pe_comp.py @@ -47,6 +47,7 @@ from flask import g import app.scodoc.sco_utils as scu from app.models import FormSemestre +from app.pe.pe_rcs import TYPES_RCS from app.scodoc import sco_formsemestre from app.scodoc.sco_logos import find_logo @@ -72,67 +73,11 @@ Descriptif d'un parcours classique BUT TODO:: A améliorer si BUT en moins de 6 semestres """ -PARCOURS = { - "S1": { - "aggregat": ["S1"], - "descr": "Semestre 1 (S1)", - }, - "S2": { - "aggregat": ["S2"], - "descr": "Semestre 2 (S2)", - }, - "1A": { - "aggregat": ["S1", "S2"], - "descr": "BUT1 (S1+S2)", - }, - "S3": { - "aggregat": ["S3"], - "descr": "Semestre 3 (S3)", - }, - "S4": { - "aggregat": ["S4"], - "descr": "Semestre 4 (S4)", - }, - "2A": { - "aggregat": ["S3", "S4"], - "descr": "BUT2 (S3+S4)", - }, - "3S": { - "aggregat": ["S1", "S2", "S3"], - "descr": "Moyenne du semestre 1 au semestre 3 (S1+S2+S3)", - }, - "4S": { - "aggregat": ["S1", "S2", "S3", "S4"], - "descr": "Moyenne du semestre 1 au semestre 4 (S1+S2+S3+S4)", - }, - "S5": { - "aggregat": ["S5"], - "descr": "Semestre 5 (S5)", - }, - "S6": { - "aggregat": ["S6"], - "descr": "Semestre 6 (S6)", - }, - "3A": { - "aggregat": ["S5", "S6"], - "descr": "3ème année (S5+S6)", - }, - "5S": { - "aggregat": ["S1", "S2", "S3", "S4", "S5"], - "descr": "Moyenne du semestre 1 au semestre 5 (S1+S2+S3+S4+S5)", - }, - "6S": { - "aggregat": ["S1", "S2", "S3", "S4", "S5", "S6"], - "descr": "Moyenne globale BUT (S1+S2+S3+S4+S5+S6)", - }, -} NBRE_SEMESTRES_DIPLOMANT = 6 AGGREGAT_DIPLOMANT = ( "6S" # aggrégat correspondant à la totalité des notes pour le diplôme ) -TOUS_LES_SEMESTRES = PARCOURS[AGGREGAT_DIPLOMANT]["aggregat"] -TOUS_LES_AGGREGATS = [cle for cle in PARCOURS.keys() if not cle.startswith("S")] -TOUS_LES_PARCOURS = list(PARCOURS.keys()) +TOUS_LES_SEMESTRES = TYPES_RCS[AGGREGAT_DIPLOMANT]["aggregat"] # ---------------------------------------------------------------------------------------- diff --git a/app/pe/pe_etudiant.py b/app/pe/pe_etudiant.py index bd0a3db4..62ec0244 100644 --- a/app/pe/pe_etudiant.py +++ b/app/pe/pe_etudiant.py @@ -37,6 +37,7 @@ Created on 17/01/2024 """ import pandas as pd +import app.pe.pe_rcs from app.models import FormSemestre, Identite, Formation from app.pe import pe_comp, pe_affichage from app.scodoc import codes_cursus @@ -121,16 +122,20 @@ class EtudiantsJuryPE: # Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris self.etudiants_diplomes = self.get_etudiants_diplomes() + """Les identités des étudiants diplômés""" + self.diplomes_ids = set(self.etudiants_diplomes.keys()) + """Les identifiants des étudiants diplômés""" self.etudiants_ids = set(self.identites.keys()) - - self.formsemestres_jury_ids = self.get_formsemestres() - """Les formsemestres (des étudiants) dont il faut calculer les moyennes""" + """Les identifiants des étudiants (diplômés, redoublants ou ayant abandonnés) à traiter""" # Les abandons (pour debug) self.abandons = self.get_etudiants_redoublants_ou_reorientes() + """Les identités des étudiants ayant redoublés ou ayant abandonnés""" + self.abandons_ids = set(self.abandons) + """Les identifiants des étudiants ayant redoublés ou ayant abandonnés""" # Synthèse pe_affichage.pe_print( @@ -142,9 +147,6 @@ class EtudiantsJuryPE: pe_affichage.pe_print( f" => {nbre_abandons} étudiants non considérés (redoublement, réorientation, abandon" ) - pe_affichage.pe_print( - f" => {len(self.formsemestres_jury_ids)} semestres dont il faut calculer la moyenne" - ) # pe_affichage.pe_print( # " => quelques étudiants futurs diplômés : " # + ", ".join([str(etudid) for etudid in list(self.etudiants_diplomes)[:10]]) @@ -270,62 +272,16 @@ class EtudiantsJuryPE: semestres_significatifs = self.get_semestres_significatifs(etudid) # Tri des semestres par numéro de semestre - for nom_sem in pe_comp.TOUS_LES_SEMESTRES: - i = int(nom_sem[1]) # le n° du semestre + for i in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT+1): # les semestres de n°i de l'étudiant: semestres_i = { fid: sem_sig for fid, sem_sig in semestres_significatifs.items() if sem_sig.semestre_id == i } - self.cursus[etudid][nom_sem] = semestres_i + self.cursus[etudid][f"S{i}"] = semestres_i - def get_trajectoire( - self, etudid: int, formsemestre_final: FormSemestre, nom_aggregat: str - ): - """Ensemble des semestres parcourus par - un étudiant pour l'amener à un semestre terminal. - Si nom_aggregat est de type "Si", limite les semestres à ceux de numéro i. - Par ex: si formsemestre_terminal est un S3 et nom_agrregat "S3", ne prend en compte que les - semestres 3. - - Si nom_aggregat est de type "iA" ou "iS" (incluant plusieurs numéros de semestres), prend en - compte les dit numéros de semestres. - - 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é). - - Les semestres parcourus sont antérieurs (en terme de date de fin) - au formsemestre_terminal. - - 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) - - if nom_aggregat.startswith("S"): # les semestres - numero_semestres_possibles = [numero_semestre_terminal] - elif nom_aggregat.endswith("A"): # les années - numero_semestres_possibles = [ - int(sem[-1]) for sem in pe_comp.PARCOURS[nom_aggregat]["aggregat"] - ] - assert numero_semestre_terminal in numero_semestres_possibles - else: # les xS = tous les semestres jusqu'à Sx (eg S1, S2, S3 pour un S3 terminal) - numero_semestres_possibles = list(range(1, numero_semestre_terminal + 1)) - - semestres_aggreges = {} - for fid, semestre in semestres_significatifs.items(): - # Semestres parmi ceux de n° possibles & qui lui sont antérieurs - if ( - semestre.semestre_id in numero_semestres_possibles - 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 @@ -353,64 +309,6 @@ class EtudiantsJuryPE: formsemestres_terminaux[fid] = trajectoire.formsemestre_final return formsemestres_terminaux - 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 (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 : - - * None : pour obtenir tous les formsemestres du jury - * 'Si' : pour obtenir les semestres de n° i (par ex. 'S1') - * 'iA' : pour obtenir les semestres de l'année i (par ex. '1A' donne ['S1, 'S2']) - * '3S', '4S' : pour obtenir les combinaisons de semestres définies par les aggrégats - - Returns: - Un dictionnaire de la forme `{fid: FormSemestre(fid)}` - - Remarque: - 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(pe_comp.AGGREGAT_DIPLOMANT) - return semestres - if 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(elmt) - semestres = semestres | semestres_elmt - return semestres - if ( - isinstance(semestres_recherches, str) - and semestres_recherches in pe_comp.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( - pe_comp.PARCOURS[semestres_recherches]["aggregat"] - ) - return semestres - if ( - isinstance(semestres_recherches, str) - and semestres_recherches in pe_comp.TOUS_LES_SEMESTRES - ): - # semestres_recherches est un nom de semestre de type S1, - # pour une recherche parmi les étudiants à prendre en compte - # dans le jury (diplômé et redoublants non diplômé) - nom_sem = semestres_recherches - semestres = {} - for etudid in self.etudiants_ids: - if self.cursus[etudid][nom_sem]: - semestres = semestres | self.cursus[etudid][nom_sem] - return semestres - - raise ValueError("Probleme de paramètres d'appel dans get_formsemestreids") def nbre_etapes_max_diplomes(self, etudids: list[int]) -> int: """Partant d'un ensemble d'étudiants, diff --git a/app/pe/pe_interclasstag.py b/app/pe/pe_interclasstag.py index 428fe3e5..1280b5ea 100644 --- a/app/pe/pe_interclasstag.py +++ b/app/pe/pe_interclasstag.py @@ -1,8 +1,8 @@ from app.comp import moy_sem from app.pe.pe_tabletags import TableTag, MoyenneTag from app.pe.pe_etudiant import EtudiantsJuryPE -from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE -from app.pe.pe_trajectoiretag import TrajectoireTag +from app.pe.pe_rcs import RCS, RCSsJuryPE +from app.pe.pe_trajectoiretag import RCSTag import pandas as pd @@ -15,8 +15,8 @@ class AggregatInterclasseTag(TableTag): self, nom_aggregat: str, etudiants: EtudiantsJuryPE, - trajectoires_jury_pe: TrajectoiresJuryPE, - trajectoires_taggues: dict[tuple, TrajectoireTag], + trajectoires_jury_pe: RCSsJuryPE, + trajectoires_taggues: dict[tuple, RCSTag], ): """ Interclasse l'ensemble des étudiants diplômés à une année @@ -44,14 +44,14 @@ class AggregatInterclasseTag(TableTag): } # Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat - self.trajectoires: dict[int, Trajectoire] = {} + self.trajectoires: dict[int, RCS] = {} """Ensemble des trajectoires associées à l'aggrégat""" - for trajectoire_id in trajectoires_jury_pe.trajectoires: - trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id] + for trajectoire_id in trajectoires_jury_pe.rcss: + trajectoire = trajectoires_jury_pe.rcss[trajectoire_id] if trajectoire_id[0] == nom_aggregat: self.trajectoires[trajectoire_id] = trajectoire - self.trajectoires_taggues: dict[int, Trajectoire] = {} + self.trajectoires_taggues: dict[int, RCS] = {} """Ensemble des trajectoires tagguées associées à l'aggrégat""" for trajectoire_id in self.trajectoires: self.trajectoires_taggues[trajectoire_id] = trajectoires_taggues[ @@ -60,7 +60,7 @@ class AggregatInterclasseTag(TableTag): # Les trajectoires suivies par les étudiants du jury, en ne gardant que # celles associées aux diplomés - self.suivi: dict[int, Trajectoire] = {} + self.suivi: dict[int, RCS] = {} """Association entre chaque étudiant et la trajectoire tagguée à prendre en compte pour l'aggrégat""" for etudid in self.diplomes_ids: diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index 79aa21ef..d600a984 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -48,17 +48,14 @@ from zipfile import ZipFile import numpy as np -from app.pe import pe_comp from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE -from app.pe.pe_tabletags import TableTag -from app.scodoc.gen_tables import SeqGenTable -from app.pe.pe_etudiant import EtudiantsJuryPE -from app.pe.pe_trajectoire import TrajectoiresJuryPE, Trajectoire +from app.pe.pe_etudiant import * +from app.pe.pe_rcs import * import app.pe.pe_comp as pe_comp from app.pe.pe_semtag import SemestreTag from app.pe.pe_interclasstag import AggregatInterclasseTag -from app.pe.pe_trajectoiretag import TrajectoireTag +from app.pe.pe_trajectoiretag import RCSTag import app.pe.pe_affichage as pe_affichage import pandas as pd @@ -160,8 +157,8 @@ class JuryPE(object): pe_affichage.pe_print( "*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants" ) - self.trajectoires = TrajectoiresJuryPE(self.diplome) - self.trajectoires.cree_trajectoires(self.etudiants) + self.trajectoires = RCSsJuryPE(self.diplome) + self.trajectoires.cree_rcss(self.etudiants) # Génère les moyennes par tags des trajectoires pe_affichage.pe_print( @@ -334,10 +331,8 @@ class JuryPE(object): df_synthese = pd.DataFrame.from_dict(donnees_etudiants, orient="index") # Ajout des aggrégats - aggregats = pe_comp.TOUS_LES_PARCOURS - - for aggregat in aggregats: - descr = pe_comp.PARCOURS[aggregat]["descr"] + for aggregat in TOUS_LES_RCS: + descr = TYPES_RCS[aggregat]["descr"] # Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag # considéré @@ -345,7 +340,7 @@ class JuryPE(object): for etudid in etudids: trajectoire = self.trajectoires.suivi[etudid][aggregat] if trajectoire: - tid = trajectoire.trajectoire_id + tid = trajectoire.rcs_id trajectoire_tagguee = self.trajectoires_tagguees[tid] if ( tag in trajectoire_tagguee.moyennes_tags @@ -476,7 +471,6 @@ class JuryPE(object): """Créé un DataFrame pour un étudiant donné par son etudid, retraçant toutes ses moyennes aux différents tag et aggrégats""" tags = self.do_tags_list(self.interclassements_taggues) - aggregats = pe_comp.TOUS_LES_PARCOURS donnees = {} @@ -484,7 +478,7 @@ class JuryPE(object): # Une ligne pour le tag donnees[tag] = {("", "", "tag"): tag} - for aggregat in aggregats: + for aggregat in TOUS_LES_RCS: # Le dictionnaire par défaut des moyennes donnees[tag] |= get_defaut_dict_synthese_aggregat( aggregat, self.diplome @@ -494,7 +488,7 @@ class JuryPE(object): trajectoire = self.trajectoires.suivi[etudid][aggregat] if trajectoire: trajectoire_tagguee = self.trajectoires_tagguees[ - trajectoire.trajectoire_id + trajectoire.rcs_id ] if tag in trajectoire_tagguee.moyennes_tags: # L'interclassement @@ -518,6 +512,27 @@ class JuryPE(object): df.sort_values(by=[("", "", "tag")], inplace=True) return df +def get_formsemestres_etudiants(etudiants: EtudiantsJuryPE) -> dict: + """Ayant connaissance des étudiants dont il faut calculer les moyennes pour + 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. + + Args: + etudiants: Les étudiants du jury PE + + Returns: + Un dictionnaire de la forme `{fid: FormSemestre(fid)}` + + """ + semestres = {} + for etudid in etudiants.etudiants_ids: + for cle in etudiants.cursus[etudid]: + if cle.startswith("S"): + semestres = semestres | etudiants.cursus[etudid][cle] + return semestres def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict: """Créé les semestres taggués, de type 'S1', 'S2', ..., pour un groupe d'étudiants donnés. @@ -536,9 +551,7 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict: """Création des semestres taggués, de type 'S1', 'S2', ...""" pe_affichage.pe_print("*** Création des semestres taggués") - formsemestres = etudiants.get_formsemestres( - semestres_recherches=pe_comp.TOUS_LES_SEMESTRES - ) + formsemestres = get_formsemestres_etudiants(etudiants) semestres_tags = {} for frmsem_id, formsemestre in formsemestres.items(): @@ -554,7 +567,7 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict: def compute_trajectoires_tag( - trajectoires: TrajectoiresJuryPE, + trajectoires: RCSsJuryPE, etudiants: EtudiantsJuryPE, semestres_taggues: dict[int, SemestreTag], ): @@ -583,11 +596,11 @@ def compute_trajectoires_tag( """ trajectoires_tagguees = {} - for trajectoire_id, trajectoire in trajectoires.trajectoires.items(): + for trajectoire_id, trajectoire in trajectoires.rcss.items(): nom = trajectoire.get_repr() pe_affichage.pe_print(f" --> Aggrégat {nom}") # Trajectoire_tagguee associée - trajectoire_tagguee = TrajectoireTag(trajectoire, semestres_taggues) + trajectoire_tagguee = RCSTag(trajectoire, semestres_taggues) # Mémorise le résultat trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee @@ -596,15 +609,15 @@ def compute_trajectoires_tag( def compute_interclassements( etudiants: EtudiantsJuryPE, - trajectoires_jury_pe: TrajectoiresJuryPE, - trajectoires_tagguees: dict[tuple, Trajectoire], + trajectoires_jury_pe: RCSsJuryPE, + trajectoires_tagguees: dict[tuple, RCS], ): """Interclasse les étudiants, (nom d') aggrégat par aggrégat, pour fournir un classement sur la promo. Le classement est établi au regard du nombre d'étudiants ayant participé au même aggrégat. """ aggregats_interclasses_taggues = {} - for nom_aggregat in pe_comp.TOUS_LES_SEMESTRES + pe_comp.TOUS_LES_AGGREGATS: + for nom_aggregat in TOUS_LES_RCS: pe_affichage.pe_print(f" --> Interclassement {nom_aggregat}") interclass = AggregatInterclasseTag( nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees @@ -613,11 +626,16 @@ def compute_interclassements( return aggregats_interclasses_taggues -def get_defaut_dict_synthese_aggregat(aggregat: str, diplome: int) -> dict: +def get_defaut_dict_synthese_aggregat(nom_rcs: str, diplome: int) -> dict: """Renvoie le dictionnaire de synthèse (à intégrer dans - un tableur excel) pour décrire les résultats d'un aggrégat""" + un tableur excel) pour décrire les résultats d'un aggrégat + + Args: + nom_rcs : Le nom du RCS visé + diplôme : l'année du diplôme + """ # L'affichage de l'aggrégat dans le tableur excel - descr = pe_comp.PARCOURS[aggregat]["descr"] + descr = get_descr_rcs(nom_rcs) nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}" donnees = { @@ -650,7 +668,7 @@ def get_defaut_dict_synthese_aggregat(aggregat: str, diplome: int) -> dict: def get_dict_synthese_aggregat( aggregat: str, - trajectoire_tagguee: TrajectoireTag, + trajectoire_tagguee: RCSTag, interclassement_taggue: AggregatInterclasseTag, etudid: int, tag: str, @@ -661,7 +679,7 @@ def get_dict_synthese_aggregat( à l'aggrégat donné et pour un tag donné""" donnees = {} # L'affichage de l'aggrégat dans le tableur excel - descr = pe_comp.PARCOURS[aggregat]["descr"] + descr = get_descr_rcs(aggregat) # La note de l'étudiant (chargement à venir) note = np.nan diff --git a/app/pe/pe_rcs.py b/app/pe/pe_rcs.py new file mode 100644 index 00000000..ab85511f --- /dev/null +++ b/app/pe/pe_rcs.py @@ -0,0 +1,253 @@ +import app.pe.pe_comp as pe_comp +import app.pe.pe_affichage as pe_affichage + +from app.models import FormSemestre +from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date + + +TYPES_RCS = { + "S1": { + "aggregat": ["S1"], + "descr": "Semestre 1 (S1)", + }, + "S2": { + "aggregat": ["S2"], + "descr": "Semestre 2 (S2)", + }, + "1A": { + "aggregat": ["S1", "S2"], + "descr": "BUT1 (S1+S2)", + }, + "S3": { + "aggregat": ["S3"], + "descr": "Semestre 3 (S3)", + }, + "S4": { + "aggregat": ["S4"], + "descr": "Semestre 4 (S4)", + }, + "2A": { + "aggregat": ["S3", "S4"], + "descr": "BUT2 (S3+S4)", + }, + "3S": { + "aggregat": ["S1", "S2", "S3"], + "descr": "Moyenne du semestre 1 au semestre 3 (S1+S2+S3)", + }, + "4S": { + "aggregat": ["S1", "S2", "S3", "S4"], + "descr": "Moyenne du semestre 1 au semestre 4 (S1+S2+S3+S4)", + }, + "S5": { + "aggregat": ["S5"], + "descr": "Semestre 5 (S5)", + }, + "S6": { + "aggregat": ["S6"], + "descr": "Semestre 6 (S6)", + }, + "3A": { + "aggregat": ["S5", "S6"], + "descr": "3ème année (S5+S6)", + }, + "5S": { + "aggregat": ["S1", "S2", "S3", "S4", "S5"], + "descr": "Moyenne du semestre 1 au semestre 5 (S1+S2+S3+S4+S5)", + }, + "6S": { + "aggregat": ["S1", "S2", "S3", "S4", "S5", "S6"], + "descr": "Moyenne globale (S1+S2+S3+S4+S5+S6)", + }, +} +"""Dictionnaire détaillant les différents regroupements cohérents +de semestres (RCS), en leur attribuant un nom et en détaillant +le nom des semestres qu'ils regroupement et l'affichage qui en sera fait +dans les tableurs de synthèse""" + +TOUS_LES_RCS_AVEC_PLUSIEURS_SEM = [cle for cle in TYPES_RCS.keys() if not cle.startswith("S")] +TOUS_LES_RCS = list(TYPES_RCS.keys()) +TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS.keys() if cle.startswith("S")] + + +class RCS: + def __init__(self, nom_rcs: str, semestre_final: FormSemestre): + """Modélise un ensemble de semestres 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 (ou 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 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 + """ + self.nom = nom_rcs + """Nom du RCS""" + + self.formsemestre_final = semestre_final + """FormSemestre terminal du RCS""" + + self.rcs_id = (nom_rcs, semestre_final.formsemestre_id) + """Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)""" + + self.semestres_aggreges = {} + """Semestres regroupés dans le RCS""" + + def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]): + """Ajout de semestres aux semestres à regrouper + + Args: + semestres: Dictionnaire ``{fid: FormSemestre(fid)}`` à ajouter + """ + self.semestres_aggreges = self.semestres_aggreges | semestres + + def get_repr(self, verbose=True) -> str: + """Représentation textuelle d'un RCS + basé 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.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}" + if verbose and noms: + repr += " - " + "+".join(noms) + return repr + + +class RCSsJuryPE: + def __init__(self, annee_diplome: int): + """Classe centralisant toutes les regroupements cohérents de + semestres (RCS) des étudiants à prendre en compte dans un jury PE + + Args: + annee_diplome: L'année de diplomation + """ + + 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.suivi: dict[int:str] = {} + """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, + son RCS : {etudid: {nom_RCS: RCS}}""" + + def cree_rcss(self, etudiants: EtudiantsJuryPE): + """Créé tous les RCS, au regard du cursus des étudiants + analysés + les mémorise dans les données de l'étudiant + + Args: + etudiants: Les étudiants à prendre en compte dans le Jury PE + """ + + 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"] + nom_semestre_terminal = noms_semestre_de_aggregat[-1] + + for etudid in etudiants.cursus: + 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.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_rcs, formsemestre_final.formsemestre_id) + if trajectoire_id not in self.rcss: + trajectoire = RCS(nom_rcs, formsemestre_final) + self.rcss[trajectoire_id] = trajectoire + else: + trajectoire = self.rcss[trajectoire_id] + + """La liste des semestres de l'étudiant à prendre en compte + pour cette trajectoire""" + semestres_a_aggreger = get_rcs_etudiant( + etudiants.cursus[etudid], formsemestre_final, nom_rcs + ) + + """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_rcs] = trajectoire + + +def get_rcs_etudiant(semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str + ): + """Ensemble des semestres parcourus par un étudiant, connaissant + les semestres de son cursus, + dans le cadre du RCS visé et ayant pour semestre terminal `formsemestre_final`. + + Si le RCS est de type "Si", limite les semestres à ceux de numéro i. + Par ex: si formsemestre_terminal est un S3 et nom_agrregat "S3", ne prend en compte que les + semestres 3. + + Si le RCS est de type "iA" ou "iS" (incluant plusieurs numéros de semestres), prend en + compte les dit numéros de semestres. + + 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é). + + Les semestres parcourus sont antérieurs (en terme de date de fin) + au formsemestre_terminal. + + Args: + cursus: Dictionnaire {fid: FormSemestre(fid)} donnant l'ensemble des semestres + dans lesquels l'étudiant a été inscrit + formsemestre_final: le semestre final visé + nom_rcs: Nom du RCS visé + """ + numero_semestre_terminal = formsemestre_final.semestre_id + # semestres_significatifs = self.get_semestres_significatifs(etudid) + semestres_significatifs = {} + for i in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT+1): + semestres_significatifs = semestres_significatifs | semestres[f"S{i}"] + + if nom_rcs.startswith("S"): # les semestres + numero_semestres_possibles = [numero_semestre_terminal] + elif nom_rcs.endswith("A"): # les années + numero_semestres_possibles = [ + int(sem[-1]) for sem in TYPES_RCS[nom_rcs]["aggregat"] + ] + assert numero_semestre_terminal in numero_semestres_possibles + else: # les xS = tous les semestres jusqu'à Sx (eg S1, S2, S3 pour un S3 terminal) + numero_semestres_possibles = list(range(1, numero_semestre_terminal + 1)) + + semestres_aggreges = {} + for fid, semestre in semestres_significatifs.items(): + # Semestres parmi ceux de n° possibles & qui lui sont antérieurs + if ( + semestre.semestre_id in numero_semestres_possibles + and semestre.date_fin <= formsemestre_final.date_fin + ): + 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_trajectoire.py b/app/pe/pe_trajectoire.py deleted file mode 100644 index 983b5e28..00000000 --- a/app/pe/pe_trajectoire.py +++ /dev/null @@ -1,145 +0,0 @@ -import app.pe.pe_comp as pe_comp -import app.pe.pe_affichage as pe_affichage - -from app.models import FormSemestre -from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date - - -class Trajectoire: - def __init__(self, nom_aggregat: str, semestre_final: FormSemestre): - """Modélise un ensemble de formsemestres d'étudiants - amenant à un semestre terminal, au sens d'un aggrégat (par ex: 'S2', '3S', '2A'). - - Si l'aggrégat est un semestre de type Si, elle stocke le (ou 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 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, ... - - Args: - nom_aggregat: Un nom d'aggrégat (par ex: '5S') - semestre_final: Le semestre final de l'aggrégat - """ - self.nom = nom_aggregat - """Nom de l'aggrégat""" - - self.formsemestre_final = semestre_final - """FormSemestre terminal de la trajectoire""" - - self.trajectoire_id = (nom_aggregat, semestre_final.formsemestre_id) - """Identifiant de la trajectoire""" - - self.semestres_aggreges = {} - """Semestres aggrégés""" - - def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]): - """Ajout de semestres aux semestres à aggréger - - Args: - semestres: Dictionnaire ``{fid: FormSemestre(fid)}`` à ajouter - """ - self.semestres_aggreges = self.semestres_aggreges | semestres - - def get_repr(self, verbose=True) -> str: - """Représentation textuelle d'une trajectoire - basée sur ses semestres aggrégés""" - - noms = [] - for fid in self.semestres_aggreges: - semestre = self.semestres_aggreges[fid] - noms.append(f"S{semestre.semestre_id}({fid})") - noms = sorted(noms) - repr = f"{self.nom} ({self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}" - if verbose and noms: - repr += " - " + "+".join(noms) - return repr - - -class TrajectoiresJuryPE: - def __init__(self, annee_diplome: int): - """Classe centralisant toutes les trajectoires des étudiants à prendre - en compte dans un jury PE - - Args: - annee_diplome: L'année de diplomation - """ - - self.annee_diplome = annee_diplome - """Toutes les trajectoires possibles""" - - self.trajectoires: dict[tuple:Trajectoire] = {} - """Ensemble des trajectoires recensées : {(aggregat, fid_terminal): Trajectoire}""" - - self.suivi: dict[int:str] = {} - """Dictionnaire associant, pour chaque étudiant et pour chaque aggrégat, - sa trajectoire : {etudid: {nom_aggregat: Trajectoire}}""" - - 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_comp.TOUS_LES_SEMESTRES + pe_comp.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_comp.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] = { - aggregat: None - for aggregat in pe_comp.TOUS_LES_SEMESTRES - + pe_comp.TOUS_LES_AGGREGATS - } - - """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, nom_aggregat - ) - - """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 - - - -def get_trajectoires_etudid(trajectoires, etudid): - """Fonction pour débuggage: renvoie la liste des trajectoires_id des - trajectoires suivies par un étudiant - """ - if etudid not in trajectoires.suivi: - pe_affichage.pe_print(f"{etudid} fait-il bien partie du jury ?") - - liste = [] - for aggregat in pe_comp.TOUS_LES_PARCOURS: - trajet = trajectoires.suivi[etudid][aggregat] - if trajet: - liste.append(trajet.trajectoire_id) - return liste - - diff --git a/app/pe/pe_trajectoiretag.py b/app/pe/pe_trajectoiretag.py index 3150daff..c3d3a05f 100644 --- a/app/pe/pe_trajectoiretag.py +++ b/app/pe/pe_trajectoiretag.py @@ -36,51 +36,48 @@ 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.pe.pe_semtag import SemestreTag import pandas as pd import numpy as np -from app.pe.pe_trajectoire import Trajectoire +from app.pe.pe_rcs import RCS from app.pe.pe_tabletags import TableTag, MoyenneTag -class TrajectoireTag(TableTag): +class RCSTag(TableTag): def __init__( - self, trajectoire: Trajectoire, semestres_taggues: dict[int, SemestreTag] + self, rcs: RCS, semestres_taggues: dict[int, SemestreTag] ): """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 + (RCS), 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' Args: - trajectoire: Une trajectoire (aggrégat+semestre terminal) + rcs: Un RCS (identifié par un nom et l'id de son semestre terminal) semestres_taggues: Les données sur les semestres taggués """ TableTag.__init__(self) - self.trajectoire_id = trajectoire.trajectoire_id - """Identifiant de la trajectoire tagguée""" + self.rcs_id = rcs.rcs_id + """Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)""" - self.trajectoire = trajectoire - """Trajectoire associée à la trajectoire tagguée""" + self.rcs = rcs + """RCS associé au RCS taggué""" self.nom = self.get_repr() - """Représentation textuelle de la trajectoire tagguée""" + """Représentation textuelle du RCS taggué""" - self.formsemestre_terminal = trajectoire.formsemestre_final + self.formsemestre_terminal = rcs.formsemestre_final """Le formsemestre terminal""" # Les résultats du formsemestre terminal nt = load_formsemestre_results(self.formsemestre_terminal) - self.semestres_aggreges = trajectoire.semestres_aggreges + self.semestres_aggreges = rcs.semestres_aggreges """Les semestres aggrégés""" self.semestres_tags_aggreges = {} @@ -114,13 +111,13 @@ class TrajectoireTag(TableTag): self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag) def __eq__(self, other): - """Egalité de 2 trajectoires tagguées sur la base de leur identifiant""" - return self.trajectoire_id == other.trajectoire_id + """Egalité de 2 RCS taggués sur la base de leur identifiant""" + return self.rcs_id == other.rcs_id def get_repr(self, verbose=False) -> str: """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle est basée)""" - return self.trajectoire.get_repr(verbose=verbose) + return self.rcs.get_repr(verbose=verbose) def compute_notes_cube(self): """Construit le cube de notes (etudid x tags x semestre_aggregé)