import app.pe.pe_comp from app.pe.rcss import pe_rcs, pe_trajectoires, pe_rcsemx import app.pe.pe_etudiant as pe_etudiant import app.pe.pe_comp as pe_comp from app.models import FormSemestre from app.pe import pe_affichage class RCSsJuryPE: """Classe centralisant tous les regroupements cohérents de semestres (RCS) des étudiants à prendre en compte dans un jury PE Args: annee_diplome: L'année de diplomation """ def __init__(self, annee_diplome: int, etudiants: pe_etudiant.EtudiantsJuryPE): self.annee_diplome = annee_diplome """Année de diplômation""" self.etudiants = etudiants """Les étudiants recensés""" self.trajectoires: dict[tuple(int, str) : pe_trajectoires.Trajectoire] = {} """Ensemble des trajectoires recensées (regroupement de (form)semestres BUT)""" self.trajectoires_suivies: dict[int:dict] = {} """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, sa Trajectoire : {etudid: {nom_RCS: Trajectoire}}""" self.semXs: dict[tuple(int, str) : pe_trajectoires.SemX] = {} """Ensemble des SemX recensés (regroupement de (form)semestre BUT de rang x) : {(nom_RCS, fid_terminal): SemX}""" self.semXs_suivis: dict[int:dict] = {} """Dictionnaire associant, pour chaque étudiant et pour chaque RCS de type Sx, son SemX : {etudid: {nom_RCS_de_type_Sx: SemX}}""" self.rcsemxs: dict[tuple(int, str) : pe_rcsemx.RCSemX] = {} """Ensemble des RCSemX (regroupement de SemX donnant les résultats aux sems de rang x) recensés : {(nom_RCS, fid_terminal): RCSemX}""" self.rcsemxs_suivis: dict[int:str] = {} """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, son RCSemX : {etudid: {nom_RCS: RCSemX}}""" def cree_trajectoires(self): """Créé toutes les trajectoires, 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 """ tous_les_aggregats = pe_rcs.TOUS_LES_RCS for etudid in self.etudiants.cursus: self.trajectoires_suivies[etudid] = self.etudiants.trajectoires[etudid] for nom_rcs in 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_semestres = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"] nom_semestre_final = noms_semestres[-1] for etudid in self.etudiants.cursus: # Le (ou les) semestre(s) marquant la fin du cursus de l'étudiant sems_final = self.etudiants.cursus[etudid][nom_semestre_final] if sems_final: # Le formsemestre final (dernier en date) de l'étudiant, # marquant la fin de son aggrégat (par ex: son dernier S3 en date) formsemestre_final = app.pe.pe_comp.get_dernier_semestre_en_date( sems_final ) # Ajout (si nécessaire) et récupération du RCS associé rcs_id = (nom_rcs, formsemestre_final.formsemestre_id) if rcs_id not in self.trajectoires: self.trajectoires[rcs_id] = pe_trajectoires.Trajectoire( nom_rcs, formsemestre_final ) rcs = self.trajectoires[rcs_id] # La liste des semestres de l'étudiant à prendre en compte # pour cette trajectoire semestres_a_aggreger = get_rcs_etudiant( self.etudiants.cursus[etudid], formsemestre_final, nom_rcs ) # Ajout des semestres au RCS rcs.add_semestres(semestres_a_aggreger) # Mémorise le RCS suivi par l'étudiant self.trajectoires_suivies[etudid][nom_rcs] = rcs self.etudiants.trajectoires[etudid][nom_rcs] = rcs def cree_semxs(self): """Créé les SemXs (trajectoires/combinaisons de semestre de même rang x), en ne conservant dans les trajectoires que les regroupements de type Sx""" self.semXs = {} for rcs_id, trajectoire in self.trajectoires.items(): if trajectoire.nom in pe_rcs.TOUS_LES_SEMESTRES: self.semXs[rcs_id] = pe_trajectoires.SemX(trajectoire) # L'association (pour chaque étudiant entre chaque Sx et le SemX associé) self.semXs_suivis = {} for etudid in self.etudiants.trajectoires: self.semXs_suivis[etudid] = { agregat: None for agregat in pe_rcs.TOUS_LES_SEMESTRES } for agregat in pe_rcs.TOUS_LES_SEMESTRES: trajectoire = self.etudiants.trajectoires[etudid][agregat] if trajectoire: rcs_id = trajectoire.rcs_id semX = self.semXs[rcs_id] self.semXs_suivis[etudid][agregat] = semX self.etudiants.semXs[etudid][agregat] = semX def cree_rcsemxs(self, options={"moyennes_ues_rcues": True}): """Créé tous les RCSemXs, 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 """ self.rcsemxs_suivis = {} self.rcsemxs = {} if "moyennes_ues_rcues" in options and options["moyennes_ues_rcues"] == False: # Pas de RCSemX généré pe_affichage.pe_print("⚠️ Pas de RCSemX générés") return # Pour tous les étudiants du jury pas_de_semestres = [] for etudid in self.trajectoires_suivies: self.rcsemxs_suivis[etudid] = { nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM } # Pour chaque aggréggat de type xA ou Sx ou xS for agregat in pe_rcs.TOUS_LES_RCS: trajectoire = self.trajectoires_suivies[etudid][agregat] if not trajectoire: self.rcsemxs_suivis[etudid][agregat] = None else: # Identifiant de la trajectoire => donnera ceux du RCSemX tid = trajectoire.rcs_id # Ajout du RCSemX if tid not in self.rcsemxs: self.rcsemxs[tid] = pe_rcsemx.RCSemX( trajectoire.nom, trajectoire.formsemestre_final ) # Récupére les SemX (RC de type Sx) associés aux semestres de son cursus # Par ex: dans S1+S2+S1+S2+S3 => les 2 S1 devient le SemX('S1'), les 2 S2 le SemX('S2'), etc.. # Les Sx pris en compte dans l'aggrégat noms_sems_aggregat = pe_rcs.TYPES_RCS[agregat]["aggregat"] semxs_a_aggreger = {} for Sx in noms_sems_aggregat: semestres_etudiants = self.etudiants.cursus[etudid][Sx] if not semestres_etudiants: pas_de_semestres += [ f"{Sx} pour {self.etudiants.identites[etudid].nomprenom}" ] else: semx_id = get_semx_from_semestres_aggreges( self.semXs, semestres_etudiants ) if not semx_id: raise ( "Il manque un SemX pour créer les RCSemX dans cree_rcsemxs" ) # Les SemX à ajouter au RCSemX semxs_a_aggreger[semx_id] = self.semXs[semx_id] # Ajout des SemX à ceux à aggréger dans le RCSemX rcsemx = self.rcsemxs[tid] rcsemx.add_semXs(semxs_a_aggreger) # Mémoire du RCSemX aux informations de suivi de l'étudiant self.rcsemxs_suivis[etudid][agregat] = rcsemx self.etudiants.rcsemXs[etudid][agregat] = rcsemx # Affichage des étudiants pour lesquels il manque un semestre pas_de_semestres = sorted(set(pas_de_semestres)) if pas_de_semestres: pe_affichage.pe_print("⚠️ Semestres manquants :") pe_affichage.pe_print( "\n".join([" " * 10 + psd for psd in pas_de_semestres]) ) def get_rcs_etudiant( semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str ) -> dict[int, FormSemestre]: """Ensemble des semestres parcourus (trajectoire) par un étudiant dans le cadre d'un RCS de type Sx, iA ou iS et ayant pour semestre terminal `formsemestre_final`. Par ex: pour un RCS "3S", dont le formsemestre_terminal est un S3, regroupe le ou les S1 qu'il a suivi (1 ou 2 si redoublement) + le ou les S2 + le ou les S3. Les semestres parcourus sont antérieurs (en terme de date de fin) au formsemestre_terminal. Args: cursus: Dictionnaire {fid: Formsemestre} 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 pe_rcs.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_semx_from_semestres_aggreges( semXs: dict[(str, int) : pe_trajectoires.SemX], semestres_a_aggreger: dict[(str, int):FormSemestre], ) -> (str, int): """Partant d'un dictionnaire de SemX (de la forme ``{ (nom_rcs, fid): SemX }, et connaissant une liste de (form)semestres suivis, renvoie l'identifiant (nom_rcs, fid) du SemX qui lui correspond. Le SemX qui correspond est tel que : * le semestre final du SemX correspond au dernier semestre en date des semestres_a_aggreger * le rang du SemX est le même que celui des semestres_aggreges * les semestres_a_aggreger (plus large, car contenant plusieurs parcours), matchent avec les semestres aggrégés par le SemX Returns: rcf_id: L'identifiant du RCF trouvé """ assert semestres_a_aggreger, "Pas de semestres à aggréger" rangs_a_aggreger = [sem.semestre_id for fid, sem in semestres_a_aggreger.items()] assert ( len(set(rangs_a_aggreger)) == 1 ), "Tous les sem à aggréger doivent être de même rang" # Le dernier semestre des semestres à regrouper dernier_sem_a_aggreger = pe_comp.get_dernier_semestre_en_date(semestres_a_aggreger) semxs_ids = [] # Au cas où il y ait plusieurs solutions for semx_id, semx in semXs.items(): # Même semestre final ? if semx.get_formsemestre_id_final() == dernier_sem_a_aggreger.formsemestre_id: # Les fids fids_a_aggreger = set(semestres_a_aggreger.keys()) # Ceux du semx fids_semx = set(semx.semestres_aggreges.keys()) if fids_a_aggreger.issubset( fids_semx ): # tous les semestres du semx correspond à des sems de la trajectoire semxs_ids += [semx_id] if len(semxs_ids) == 0: return None # rien trouvé elif len(semxs_ids) == 1: return semxs_ids[0] else: raise "Plusieurs solutions :)"