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): self.annee_diplome = annee_diplome """Année de diplômation""" 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, etudiants: pe_etudiant.EtudiantsJuryPE): """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_SEMESTRES + pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM ) for etudid in etudiants.cursus: self.trajectoires_suivies[etudid] = { aggregat: None for aggregat in tous_les_aggregats } 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_semestre_de_aggregat = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"] nom_semestre_terminal = noms_semestre_de_aggregat[-1] for etudid in etudiants.cursus: # Le formsemestre terminal (dernier en date) associé au # semestre marquant la fin de l'aggrégat # (par ex: son dernier S3 en date) trajectoire = etudiants.cursus[etudid][nom_semestre_terminal] if trajectoire: formsemestre_final = app.pe.pe_comp.get_dernier_semestre_en_date( trajectoire ) # Ajout ou 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( 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 def _aff_trajectoires(self, etudiants: pe_etudiant.EtudiantsJuryPE): """Affiche les chemins trouvés pour debug""" # Affichage pour debug jeunes = list(enumerate(self.trajectoires_suivies)) for no_etud, etudid in jeunes: etat = "⛔" if etudid in etudiants.abandons_ids else "✅" pe_affichage.pe_print( f"--> {etat} {etudiants.identites[etudid].nomprenom} (#{etudid}) :" ) for nom_rcs, rcs in self.trajectoires_suivies[etudid].items(): if rcs: pe_affichage.pe_print(f" > RCS ⏯️{nom_rcs}: {rcs.get_repr()}") def cree_semxs(self, etudiants: pe_etudiant.EtudiantsJuryPE): """Créé les 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 and trajectoire.nom in pe_rcs.TOUS_LES_SEMESTRES: self.semXs[rcs_id] = pe_trajectoires.SemX(trajectoire) self.semXs_suivis = {} for etudid in self.trajectoires_suivies: self.semXs_suivis[etudid] = { nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES } for nom_rcs, trajectoire in self.trajectoires_suivies[etudid].items(): if trajectoire and nom_rcs in pe_rcs.TOUS_LES_SEMESTRES: rcs_id = trajectoire.rcs_id self.semXs_suivis[etudid][nom_rcs] = self.semXs[rcs_id] def _aff_semxs_suivis(self, etudiants: pe_etudiant.EtudiantsJuryPE): """Affichage des SemX pour debug""" jeunes = list(enumerate(self.semXs_suivis)) for no_etud, etudid in jeunes: etat = "⛔" if etudid in etudiants.abandons_ids else "✅" pe_affichage.pe_print( f"--> {etat} {etudiants.identites[etudid].nomprenom} :" ) for nom_rcs, rcs in self.semXs_suivis[etudid].items(): if rcs: pe_affichage.pe_print(f" > SemX ⏯️{nom_rcs}: {rcs.get_repr()}") vides = [] for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES: les_semX_suivis = [] for no_etud, etudid in jeunes: if self.semXs_suivis[etudid][nom_rcs]: les_semX_suivis.append(self.semXs_suivis[etudid][nom_rcs]) if not les_semX_suivis: vides += [nom_rcs] vides = sorted(list(set(vides))) pe_affichage.pe_print(f"⚠️ SemX sans données : {', '.join(vides)}") def cree_rcsemxs(self, etudiants: pe_etudiant.EtudiantsJuryPE): """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 = {} # 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 } # Recopie des SemX & des suivis associés => est-ce utile ? # for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES: # trajectoire = self.semXs_suivis[etudid][nom_rcs] # if trajectoire: # self.rcsemxs[trajectoire.rcs_id] = trajectoire # self.rcsemxs_suivis[etudid][nom_rcs] = trajectoire # Pour chaque aggréggat de type xA ou Sx tous_les_agregats = pe_rcs.TOUS_LES_RCS for nom_rcs in tous_les_agregats: trajectoire = self.trajectoires_suivies[etudid][nom_rcs] if not trajectoire: self.rcsemxs_suivis[etudid][nom_rcs] = 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[nom_rcs]["aggregat"] semxs_a_aggreger = {} for Sx in noms_sems_aggregat: semestres_etudiants = etudiants.cursus[etudid][Sx] if not semestres_etudiants: pas_de_semestres += [ f"{Sx} pour {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][nom_rcs] = 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 _aff_rcsemxs_suivis(self, etudiants): """Affiche les RCSemX suivis par les étudiants""" # Affichage pour debug jeunes = list(enumerate(self.rcsemxs_suivis.keys())) for no_etud, etudid in jeunes: etat = "⛔" if etudid in etudiants.abandons_ids else "✅" pe_affichage.pe_print( f"-> {etat} {etudiants.identites[etudid].nomprenom} :" ) for nom_rcs, rcs in self.rcsemxs_suivis[etudid].items(): if rcs: pe_affichage.pe_print(f" > RCSemX ⏯️{nom_rcs}: {rcs.get_repr()}") vides = [] for nom_rcs in pe_rcs.TOUS_LES_RCS: les_rcssemX_suivis = [] for no_etud, etudid in jeunes: if self.rcsemxs_suivis[etudid][nom_rcs]: les_rcssemX_suivis.append(self.rcsemxs_suivis[etudid][nom_rcs]) if not les_rcssemX_suivis: vides += [nom_rcs] vides = sorted(list(set(vides))) pe_affichage.pe_print(f"⚠️ RCSemX vides : {', '.join(vides)}") 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 :)"