diff --git a/app/pe/pe_comp.py b/app/pe/pe_comp.py index 4f4ba8ed..0ab973f4 100644 --- a/app/pe/pe_comp.py +++ b/app/pe/pe_comp.py @@ -47,7 +47,7 @@ from flask import g import app.scodoc.sco_utils as scu from app.models import FormSemestre -from app.pe.rcss.rcss_constantes import TYPES_RCS +from app.pe.rcss.pe_rcs import TYPES_RCS from app.scodoc import sco_formsemestre from app.scodoc.sco_logos import find_logo @@ -317,3 +317,23 @@ def find_index_and_columns_communs( colonnes2 = df2.columns colonnes_communes = list(set(colonnes1) & set(colonnes2)) return indices_communs, colonnes_communes + + +def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSemestre: + """Renvoie le dernier semestre en **date de fin** d'un dictionnaire + de semestres (potentiellement non trié) de la forme ``{fid: FormSemestre(fid)}``. + + Args: + semestres: Un dictionnaire de semestres + + Return: + Le FormSemestre du semestre le plus récent + """ + if semestres: + fid_dernier_semestre = list(semestres.keys())[0] + dernier_semestre: FormSemestre = semestres[fid_dernier_semestre] + for fid in semestres: + if semestres[fid].date_fin > dernier_semestre.date_fin: + dernier_semestre = semestres[fid] + return dernier_semestre + return None diff --git a/app/pe/pe_etudiant.py b/app/pe/pe_etudiant.py index 35e9f942..94e17e43 100644 --- a/app/pe/pe_etudiant.py +++ b/app/pe/pe_etudiant.py @@ -59,10 +59,10 @@ class EtudiantsJuryPE: self.identites: dict[int, Identite] = {} # ex. ETUDINFO_DICT "Les identités des étudiants traités pour le jury" - self.trajectoires: dict[int, dict] = {} + self.cursus: dict[int, dict] = {} "Les cursus (semestres suivis, abandons) des étudiants" - self.trajectoires = {} + self.cursus = {} """Les trajectoires/chemins de semestres suivis par les étudiants pour atteindre un aggrégat donné (par ex: 3S=S1+S2+S3 à prendre en compte avec d'éventuels redoublements)""" @@ -164,7 +164,7 @@ class EtudiantsJuryPE: """ etudids = [ etudid - for etudid, cursus_etud in self.trajectoires.items() + for etudid, cursus_etud in self.cursus.items() if cursus_etud["diplome"] == self.annee_diplome and cursus_etud["abandon"] is False ] @@ -181,18 +181,14 @@ class EtudiantsJuryPE: """ etudids = [ etudid - for etudid, cursus_etud in self.trajectoires.items() + for etudid, cursus_etud in self.cursus.items() if cursus_etud["diplome"] != self.annee_diplome or cursus_etud["abandon"] is True ] etudiants = {etudid: self.identites[etudid] for etudid in etudids} return etudiants - def analyse_etat_etudiant( - self, - etudid: int, - cosemestres: dict[int, FormSemestre] - ): + def analyse_etat_etudiant(self, etudid: int, cosemestres: dict[int, FormSemestre]): """Analyse le cursus d'un étudiant pouvant être : * l'un de ceux sur lesquels le jury va statuer (année de diplômation du jury considéré) @@ -225,7 +221,7 @@ class EtudiantsJuryPE: if formsemestre.formation.is_apc() } - self.trajectoires[etudid] = { + self.cursus[etudid] = { "etudid": etudid, # les infos sur l'étudiant "etat_civil": identite.etat_civil, # Ajout à la table jury "nom": identite.nom, @@ -241,16 +237,16 @@ class EtudiantsJuryPE: } # Si l'étudiant est succeptible d'être diplomé - if self.trajectoires[etudid]["diplome"] == self.annee_diplome: + if self.cursus[etudid]["diplome"] == self.annee_diplome: # Est-il démissionnaire : charge son dernier semestre pour connaitre son état ? dernier_semes_etudiant = formsemestres[0] res = load_formsemestre_results(dernier_semes_etudiant) etud_etat = res.get_etud_etat(etudid) if etud_etat == scu.DEMISSION: - self.trajectoires[etudid]["abandon"] = True + self.cursus[etudid]["abandon"] = True else: # Est-il réorienté ou a-t-il arrêté (volontairement) sa formation ? - self.trajectoires[etudid]["abandon"] = arret_de_formation( + self.cursus[etudid]["abandon"] = arret_de_formation( identite, cosemestres ) @@ -270,7 +266,7 @@ class EtudiantsJuryPE: Un dictionnaire ``{fid: FormSemestre(fid)}`` dans lequel les semestres amènent à une diplômation antérieur à celle de la diplômation visée par le jury jury """ - semestres_etudiant = self.trajectoires[etudid]["formsemestres"] + semestres_etudiant = self.cursus[etudid]["formsemestres"] semestres_significatifs = {} for fid in semestres_etudiant: semestre = semestres_etudiant[fid] @@ -297,11 +293,9 @@ class EtudiantsJuryPE: for fid, sem_sig in semestres_significatifs.items() if sem_sig.semestre_id == i } - self.trajectoires[etudid][f"S{i}"] = semestres_i + self.cursus[etudid][f"S{i}"] = semestres_i - def get_formsemestres_finals_des_rcs( - self, nom_rcs: str - ) -> dict[int, FormSemestre]: + def get_formsemestres_finals_des_rcs(self, nom_rcs: str) -> dict[int, FormSemestre]: """Pour un nom de RCS donné, ensemble des formsemestres finals possibles pour les RCS. Par ex. un RCS '3S' incluant S1+S2+S3 a pour semestre final un S3. Les formsemestres finals obtenus traduisent : @@ -321,7 +315,7 @@ class EtudiantsJuryPE: Un dictionnaire ``{fid: FormSemestre(fid)}`` """ formsemestres_terminaux = {} - for trajectoire_aggr in self.trajectoires.values(): + for trajectoire_aggr in self.cursus.values(): trajectoire = trajectoire_aggr[nom_rcs] if trajectoire: # Le semestre terminal de l'étudiant de l'aggrégat @@ -338,7 +332,7 @@ class EtudiantsJuryPE: """ nbres_semestres = [] for etudid in etudids: - nbres_semestres.append(self.trajectoires[etudid]["nb_semestres"]) + nbres_semestres.append(self.cursus[etudid]["nb_semestres"]) if not nbres_semestres: return 0 return max(nbres_semestres) @@ -359,7 +353,7 @@ class EtudiantsJuryPE: for etudid in etudids: etudiant = self.identites[etudid] - cursus = self.trajectoires[etudid] + cursus = self.cursus[etudid] formsemestres = cursus["formsemestres"] if cursus["diplome"]: @@ -549,9 +543,9 @@ def arret_de_formation(etud: Identite, cosemestres: dict[int, FormSemestre]) -> non_inscrit_a = [ rang for rang in etat_inscriptions if not etat_inscriptions[rang] ] - affichage = ",".join([f"S{val}" for val in non_inscrit_a]) + affichage = ", ".join([f"S{val}" for val in non_inscrit_a]) pe_affichage.pe_print( - f"{etud.etat_civil} ({etud.etudid}) considéré en abandon car non inscrit dans un (ou des) semestre(s) {affichage} amenant à diplômation" + f"--> ⛔ {etud.etat_civil} ({etud.etudid}), non inscrit dans {affichage} amenant à diplômation" ) return est_demissionnaire @@ -593,26 +587,6 @@ def arret_de_formation(etud: Identite, cosemestres: dict[int, FormSemestre]) -> # return False -def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSemestre: - """Renvoie le dernier semestre en **date de fin** d'un dictionnaire - de semestres (potentiellement non trié) de la forme ``{fid: FormSemestre(fid)}``. - - Args: - semestres: Un dictionnaire de semestres - - Return: - Le FormSemestre du semestre le plus récent - """ - if semestres: - fid_dernier_semestre = list(semestres.keys())[0] - dernier_semestre: FormSemestre = semestres[fid_dernier_semestre] - for fid in semestres: - if semestres[fid].date_fin > dernier_semestre.date_fin: - dernier_semestre = semestres[fid] - return dernier_semestre - return None - - def etapes_du_cursus( semestres: dict[int, FormSemestre], nbre_etapes_max: int ) -> list[str]: @@ -679,4 +653,3 @@ def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str: description.append(f"({semestre.formsemestre_id})") return " ".join(description) - diff --git a/app/pe/pe_interclasstag.py b/app/pe/pe_interclasstag.py index 91105066..9d5b998d 100644 --- a/app/pe/pe_interclasstag.py +++ b/app/pe/pe_interclasstag.py @@ -83,8 +83,8 @@ class RCSInterclasseTag(TableTag): # celles associées à l'aggrégat self.rcss: dict[int, pe_rcs.RCS] = {} """Ensemble des trajectoires associées à l'aggrégat""" - for trajectoire_id in rcss_jury_pe.rcss: - trajectoire = rcss_jury_pe.rcss[trajectoire_id] + for trajectoire_id in rcss_jury_pe.trajectoires: + trajectoire = rcss_jury_pe.trajectoires[trajectoire_id] if trajectoire_id[0] == nom_rcs: self.rcss[trajectoire_id] = trajectoire @@ -99,7 +99,7 @@ class RCSInterclasseTag(TableTag): """Association entre chaque étudiant et la trajectoire tagguée à prendre en compte pour l'aggrégat""" for etudid in self.diplomes_ids: - self.suivi[etudid] = rcss_jury_pe.rcss_suivis[etudid][nom_rcs] + self.suivi[etudid] = rcss_jury_pe.trajectoires_suivies[etudid][nom_rcs] self.tags_sorted = self.do_taglist() """Liste des tags (triés par ordre alphabétique)""" diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index 168cdb9b..030e3ea7 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -50,17 +50,15 @@ from zipfile import ZipFile import numpy as np import pandas as pd -import app.pe.rcss.rcss_constantes as rcss_constants +from app.pe.rcss import pe_rcs from app.pe import pe_sxtag from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE import app.pe.pe_affichage as pe_affichage import app.pe.pe_etudiant as pe_etudiant -import app.pe.rcss.pe_rcs as pe_rcs from app.pe.pe_rcstag import RCSTag from app.pe.pe_ressemtag import ResSemBUTTag from app.pe.pe_interclasstag import RCSInterclasseTag import app.pe.pe_rcss_jury as pe_rcss_jury -import app.pe.rcss.rcss_constantes as rcss_constantes class JuryPE(object): @@ -107,11 +105,11 @@ class JuryPE(object): try: self._gen_xls_diplomes(zipfile) self._gen_xls_ressembuttags(zipfile) - self._gen_rcss() - self._gen_rcsf() + self._gen_trajectoires() + self._gen_semXs() self._gen_xls_sxtags(zipfile) - self._gen_rcrcfs() - self._gen_xls_rcrcss_tags(zipfile) + # self._gen_rcsemxs() + # self._gen_xls_rcrcss_tags(zipfile) # self._gen_xls_interclassements_rcss(zipfile) # self._gen_xls_synthese_jury_par_tag(zipfile) # self._gen_xls_synthese_par_etudiant(zipfile) @@ -185,35 +183,40 @@ class JuryPE(object): path="details", ) - def _gen_rcss(self): - """Génère les RCS (attribut `rcss_jury`), combinaisons de semestres - suivis par les étudiants au sens d'un nom de RCS (par ex: 'S2' ou '3S'). + def _gen_trajectoires(self): + """Génère l'ensemble des trajectoires (RCS), qui traduisent les différents + chemins au sein des (form)semestres pour atteindre la cible d'un + RCS (par ex: 'S2' ou '3S'). """ pe_affichage.pe_print( - "*** Génère les RCS (différentes combinaisons de semestres) des étudiants" + "*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants" ) - self.rcss_jury.cree_rcss(self.etudiants) + self.rcss_jury.cree_trajectoires(self.etudiants) - def _gen_rcsf(self): - """Génère les RCF, regroupement de semestres de type Sx pour préparer - le calcul des moyennes par Sx""" + def _gen_semXs(self): + """Génère les SemXs (trajectoires/combinaisons de semestre de même rang x) + qui traduisent les différents chemins des étudiants pour valider un semestre Sx. + """ # Génère les regroupements de semestres de type Sx pe_affichage.pe_print( - "*** Génère les RCSValid (RCS de même Sx donnant lieu à validation du semestre)" + "*** Génère les SemXs (RCS de même Sx donnant lieu à validation du semestre)" ) - self.rcss_jury.cree_rcfs(self.etudiants) + self.rcss_jury.cree_semxs(self.etudiants) + self.rcss_jury._aff_semxs_suivis(self.etudiants) def _gen_xls_sxtags(self, zipfile: ZipFile): """Génère les semestres taggués en s'appuyant sur les RCF de type Sx (pour identifier les redoublements impactant les semestres taggués). """ # Génère les moyennes des RCS de type Sx - pe_affichage.pe_print("*** Calcule les moyennes des SxTag") + pe_affichage.pe_print( + "*** Calcule les moyennes des SxTag (moyennes d'un SemX/RCS de type Sx)" + ) # Les SxTag (moyenne de Sx par UE) self.sxtags = {} - for rcf_id, rcf in self.rcss_jury.rcfs.items(): + for rcf_id, rcf in self.rcss_jury.semXs.items(): # SxTag traduisant le RCF sxtag_id = rcf_id @@ -242,13 +245,15 @@ class JuryPE(object): path="details", ) - def _gen_rcrcfs(self): + def _gen_rcsemxs(self): """Génère les regroupements cohérents de RCFs qu'ont suivi chaque étudiant""" pe_affichage.pe_print( - "*** Génère les RCRCF (regroupements de RCF de type Sx) amenant du S1 à un semestre final***" + "*** Génère les RCSemX (regroupements cohérents de données" + " extraites des SemX) amenant du S1 à un semestre final***" ) - self.rcss_jury.cree_rcrcfs(self.etudiants) + self.rcss_jury.cree_rcsemxs(self.etudiants) + self.rcss_jury._aff_rcsemxs_suivis(self.etudiants) def _gen_xls_rcrcss_tags(self, zipfile: ZipFile): """Génère les RCS taggués traduisant les moyennes (orientées compétences) @@ -273,7 +278,7 @@ class JuryPE(object): pe_affichage.pe_print("*** Calcule les moyennes des RC de RCFS") self.rcss_tags = {} - for rcs_id, rcrcf in self.rcss_jury.rcrcfs.items(): + for rcs_id, rcrcf in self.rcss_jury.rcsemxs.items(): self.rcss_tags[rcs_id] = RCSTag(rcrcf, self.sxtags) # Intègre le bilan des trajectoires tagguées au zip final @@ -458,13 +463,13 @@ class JuryPE(object): # Ajout des aggrégats for aggregat in pe_rcs.TOUS_LES_RCS: - descr = rcss_constantes.TYPES_RCS[aggregat]["descr"] + descr = app.pe.rcss.pe_rcs.TYPES_RCS[aggregat]["descr"] # Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag # considéré trajectoires_tagguees = [] for etudid in etudids: - trajectoire = self.rcss_jury.rcss_suivis[etudid][aggregat] + trajectoire = self.rcss_jury.trajectoires_suivies[etudid][aggregat] if trajectoire: tid = trajectoire.sxtag_id trajectoire_tagguee = self.rcss_tags[tid] @@ -610,7 +615,7 @@ class JuryPE(object): ) # La trajectoire de l'étudiant sur l'aggrégat - trajectoire = self.rcss_jury.rcss_suivis[etudid][aggregat] + trajectoire = self.rcss_jury.trajectoires_suivies[etudid][aggregat] if trajectoire: trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id] if tag in trajectoire_tagguee.moyennes_tags: @@ -651,9 +656,9 @@ def get_formsemestres_etudiants(etudiants: pe_etudiant.EtudiantsJuryPE) -> dict: """ semestres = {} for etudid in etudiants.etudiants_ids: - for cle in etudiants.trajectoires[etudid]: + for cle in etudiants.cursus[etudid]: if cle.startswith("S"): - semestres = semestres | etudiants.trajectoires[etudid][cle] + semestres = semestres | etudiants.cursus[etudid][cle] return semestres diff --git a/app/pe/pe_rcss_jury.py b/app/pe/pe_rcss_jury.py index 58697cb2..65e423b0 100644 --- a/app/pe/pe_rcss_jury.py +++ b/app/pe/pe_rcss_jury.py @@ -1,9 +1,7 @@ - -import app.pe.rcss.pe_rcf as pe_rcf -import app.pe.rcss.pe_rcrcf as pe_rcrcf +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 -import app.pe.rcss.rcss_constantes as rcss_constantes from app.models import FormSemestre from app.pe import pe_affichage @@ -20,159 +18,198 @@ class RCSsJuryPE: self.annee_diplome = annee_diplome """Année de diplômation""" - self.rcss: dict[tuple(int, str): pe_rcf.RCF] = {} - """Ensemble des RCS recensés""" + self.trajectoires: dict[tuple(int, str) : pe_trajectoires.Trajectoire] = {} + """Ensemble des trajectoires recensées (regroupement de (form)semestres BUT)""" - self.rcss_suivis: dict[int:dict] = {} + self.trajectoires_suivies: dict[int:dict] = {} """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, - son RCS : {etudid: {nom_RCS: RCS}}""" + sa Trajectoire : {etudid: {nom_RCS: Trajectoire}}""" - self.rcfs: dict[tuple(int, str) : pe_rcf.RCF] = {} - """Ensemble des RCF recensés : {(nom_RCS, fid_terminal): RCF}""" + 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.rcfs_suivis: dict[int:dict] = {} + 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 RCS : {etudid: {nom_RCS: RCF}}""" + son RCSemX : {etudid: {nom_RCS: RCSemX}}""" - self.rcrcfs: dict[tuple(int, str) : pe_rcrcf.RCRCF] = {} - """Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCRCF}""" - - self.rcrcfs_suivis: dict[int:str] = {} - """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, - son RCRCF : {etudid: {nom_RCS: RCSx}}""" - - def cree_rcss(self, etudiants: pe_etudiant.EtudiantsJuryPE): - """Créé tous les RCS, au regard du cursus des étudiants + 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 = rcss_constantes.TOUS_LES_SEMESTRES + rcss_constantes.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM - for etudid in etudiants.trajectoires: - self.rcss_suivis[etudid] = { - aggregat: None - for aggregat in tous_les_aggregats + 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 = rcss_constantes.TYPES_RCS[nom_rcs]["aggregat"] + noms_semestre_de_aggregat = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"] nom_semestre_terminal = noms_semestre_de_aggregat[-1] - for etudid in etudiants.trajectoires: + 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.trajectoires[etudid][nom_semestre_terminal] + trajectoire = etudiants.cursus[etudid][nom_semestre_terminal] if trajectoire: - formsemestre_final = pe_etudiant.get_dernier_semestre_en_date(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.rcss: - self.rcss[rcs_id] = pe_rcf.RCF(nom_rcs, formsemestre_final) - rcs = self.rcss[rcs_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.trajectoires[etudid], formsemestre_final, nom_rcs + etudiants.cursus[etudid], formsemestre_final, nom_rcs ) # Ajout des semestres au RCS - rcs.add_semestres_a_aggreger(semestres_a_aggreger) + rcs.add_semestres(semestres_a_aggreger) # Mémorise le RCS suivi par l'étudiant - self.rcss_suivis[etudid][nom_rcs] = rcs + self.trajectoires_suivies[etudid][nom_rcs] = rcs # Affichage pour debug - jeunes = list(enumerate(self.rcss_suivis)) + jeunes = list(enumerate(self.trajectoires_suivies)) for no_etud, etudid in jeunes[:20]: - pe_affichage.pe_print(f"--> {etudiants.identites[etudid].nomprenom} (#{etudid}) :") - for nom_rcs, rcs in self.rcss_suivis[etudid].items(): + pe_affichage.pe_print( + f"--> {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_rcfs(self, etudiants: pe_etudiant.EtudiantsJuryPE): - """Créé les RCFs en ne conservant dans les RCS que les regroupements + 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.rcfs = {} - for rcs_id, rcs in self.rcss.items(): - if rcs and rcs.nom in rcss_constantes.TOUS_LES_SEMESTRES: - self.rcfs[rcs_id] = rcs + 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) - for etudid in self.rcss_suivis: - for nom_rcs, rcs in self.rcss_suivis[etudid].items(): - if rcs and nom_rcs in rcss_constantes.TOUS_LES_SEMESTRES: - if etudid not in self.rcfs_suivis: - self.rcfs_suivis[etudid] = {} - self.rcfs_suivis[etudid][nom_rcs] = rcs + 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 + } - # Affichage pour debug - jeunes = list(enumerate(self.rcfs_suivis)) + 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)) + vides = [] for no_etud, etudid in jeunes[:20]: pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :") - for nom_rcs, rcs in self.rcfs_suivis[etudid].items(): + for nom_rcs, rcs in self.semXs_suivis[etudid].items(): if rcs: - pe_affichage.pe_print(f" > RCSValid {nom_rcs}: {rcs.get_repr()}") + pe_affichage.pe_print(f" > SemX {nom_rcs}: {rcs.get_repr()}") else: - pe_affichage.pe_print(f" > RCSValid {nom_rcs}: ") + vides += [nom_rcs] + vides = sorted(list(set(vides))) + pe_affichage.pe_print(f"-> ⚠ SemX vides : {', '.join(vides)}") - def cree_rcrcfs(self, etudiants: pe_etudiant.EtudiantsJuryPE): - """Créé tous les RCRCF, au regard du cursus des étudiants + 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 = {nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_RCS} + self.rcsemxs = {} # Pour tous les étudiants du jury - for etudid in self.rcss_suivis: - self.rcrcfs_suivis[etudid] = {} + for etudid in self.trajectoires_suivies: + self.rcsemxs_suivis[etudid] = {} - for nom_rcs, rcf in self.rcfs_suivis[etudid].items(): # Pour chaque RCS - semestres_a_aggreger = rcf.semestres_aggreges + # Recopie des SemX & des suivis associés + 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 - # Tri des semestres par rang - semestres_tries = pe_comp.tri_semestres_par_rang(semestres_a_aggreger) - - # Récupére les RCFs de type Sx traduisant sa trajectoire - rcfs_a_aggreger = {} - for semestres_du_rang in semestres_tries.values(): - if semestres_du_rang: - rcf_id = get_rcf_from_semestres_aggreges( - self.rcfs, semestres_du_rang + # Pour chaque aggréggat de type xA ou Sx + for nom_rcs in pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM: + 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 ) - if not rcf_id: - raise ValueError( - "Il manque un RCF pour créer les RCRCFs dans cree_rcrcfs" + + # 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] + + 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" ) - rcfs_a_aggreger[rcf_id] = self.rcfs[rcf_id] + # Les SemX à ajouter au RCSemX + semxs_a_aggreger[semx_id] = self.semXs[semx_id] - # Ajout du RCRCF - if rcf_id not in self.rcrcfs: - rcf_nom = rcf_id[0] - self.rcrcfs[rcf_id] = pe_rcrcf.RCRCF(rcf_nom, rcf.formsemestre_final) - rcrcf = self.rcrcfs[rcf_id] + # Ajout des SemX à ceux à aggréger dans le RCSemX + rcsemx = self.rcsemxs[tid] + rcsemx.add_semXs(semxs_a_aggreger) - # Ajout des RCFs au RCRCF - rcrcf.add_rcfs_a_aggreger(rcfs_a_aggreger) - - # Mémoire la trajectoire RCRCF suivie par l'étudiant - nom_rcs = rcrcf.nom - self.rcrcfs_suivis[etudid][nom_rcs] = rcrcf + # Mémoire du RCSemX aux informations de suivi de l'étudiant + self.rcsemxs_suivis[etudid][nom_rcs] = rcsemx + def _aff_rcsemxs_suivis(self, etudiants): + """Affiche les RCSemX suivis par les étudiants""" # Affichage pour debug - jeunes = list(enumerate(self.rcrcfs_suivis)) + jeunes = list(enumerate(self.rcsemxs_suivis)) + vides = [] for no_etud, etudid in jeunes[:20]: pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :") - for nom_rcs, rcs in self.rcrcfs_suivis[etudid].items(): + for nom_rcs, rcs in self.rcsemxs_suivis[etudid].items(): if rcs: - pe_affichage.pe_print(f" > RCRCF {nom_rcs}: {rcs.get_repr()}") + pe_affichage.pe_print(f" > RCSemX {nom_rcs}: {rcs.get_repr()}") else: - pe_affichage.pe_print(f" > RCRCF {nom_rcs}: !!! ") - + vides += [f"{nom_rcs}"] + pe_affichage.pe_print(f"-> ⚠ RCSemX vides : {', '.join(list(set(vides)))}") def get_rcs_etudiant( @@ -204,7 +241,7 @@ def get_rcs_etudiant( numero_semestres_possibles = [numero_semestre_terminal] elif nom_rcs.endswith("A"): # les années numero_semestres_possibles = [ - int(sem[-1]) for sem in rcss_constantes.TYPES_RCS[nom_rcs]["aggregat"] + 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) @@ -221,25 +258,52 @@ def get_rcs_etudiant( return semestres_aggreges -def get_rcf_from_semestres_aggreges( - rcfs: dict[(str, int):pe_rcf.RCF], semestres_a_aggreges: list[FormSemestre] - ) -> (str, int): - """Partant d'un dictionnaire de RCFs (de la forme - ``{ (nom_rcs, fid): RCF }, et connaissant une liste - de (form)semestres à aggréger, renvoie l'identifiant - (nom_rcs, fid) du RCFs qui lui correspond (c'est à dire celui dont - les semestres_aggregés par le RCF sont les même que les - semestres_a_aggreger. +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. - Returns: - rcf_id: L'identifiant du RCF trouvé + 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é """ - fids_semestres_a_aggreger = set( - [frms.formsemestre_id for frms in semestres_a_aggreges] - ) - for rcf_id, rcf in rcfs.items(): - fids_rcf = set(rcf.semestres_aggreges) - if fids_rcf == fids_semestres_a_aggreger: - return rcf_id - return None + 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 :)" diff --git a/app/pe/pe_rcstag.py b/app/pe/pe_rcstag.py index 43425941..d675483f 100644 --- a/app/pe/pe_rcstag.py +++ b/app/pe/pe_rcstag.py @@ -42,7 +42,7 @@ from app.pe import pe_affichage import pandas as pd import numpy as np import app.pe.rcss.pe_rcs as pe_rcs -import app.pe.rcss.pe_rcrcf as pe_rcrcf +import app.pe.rcss.pe_rcsemx as pe_rcrcf import app.pe.pe_sxtag as pe_sxtag import app.pe.pe_comp as pe_comp from app.pe.pe_tabletags import TableTag @@ -66,7 +66,7 @@ class RCSTag(TableTag): self.rcs_id: tuple(str, int) = rcrcf.rcs_id """Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)""" - self.rcrcf: pe_rcrcf.RCRCF = rcrcf + self.rcrcf: pe_rcrcf.RCSemX = rcrcf """RCRCF associé au RCS taggué""" self.nom = self.get_repr() @@ -82,12 +82,12 @@ class RCSTag(TableTag): pe_affichage.pe_print(f"-> {self.get_repr(verbose=True)}") # Les données aggrégés (RCRCF + SxTags - self.rcfs_aggreges = rcrcf.rcfs_aggreges + self.rcsemxs_aggreges = rcrcf.rcsemxs_aggreges """Les RCFs aggrégés""" self.sxstags = {} """Les SxTag associés aux RCF aggrégés""" try: - for rcf_id in self.rcfs_aggreges: + for rcf_id in self.rcsemxs_aggreges: self.sxstags[rcf_id] = sxstags[rcf_id] except: raise ValueError("Semestres SxTag manquants") diff --git a/app/pe/pe_sxtag.py b/app/pe/pe_sxtag.py index f96b7807..b7600bca 100644 --- a/app/pe/pe_sxtag.py +++ b/app/pe/pe_sxtag.py @@ -43,14 +43,14 @@ import numpy as np from app.pe.pe_tabletags import TableTag from app.pe.pe_moytag import MoyennesTag -import app.pe.rcss.pe_rcf as pe_rcf +import app.pe.rcss.pe_trajectoires as pe_trajectoires class SxTag(TableTag): def __init__( self, sxtag_id: (str, int), - rcf: pe_rcf.RCF, + semx: pe_trajectoires.SemX, ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag], ): """Calcule les moyennes/classements par tag d'un semestre de type 'Sx' @@ -86,13 +86,22 @@ class SxTag(TableTag): self.sxtag_id: (str, int) = sxtag_id """Identifiant du SxTag de la forme (nom_Sx, fid_semestre_final)""" + assert ( + len(self.sxtag_id) == 2 + and isinstance(self.sxtag_id[0], str) + and isinstance(self.sxtag_id[1], int) + ), "Format de l'identifiant du SxTag non respecté" - self.rcf = rcf - """Le RCF sur lequel il s'appuie""" - assert rcf.rcs_id == sxtag_id, "Problème de correspondance SxTag/RCF" + self.nom_rcs = sxtag_id[0] - # Les resultats des semestres taggués à prendre en compte dans le RCF - self.ressembuttags = {fid: ressembuttags[fid] for fid in rcf.semestres_aggreges} + self.semx = semx + """Le SemX sur lequel il s'appuie""" + assert semx.rcs_id == sxtag_id, "Problème de correspondance SxTag/SemX" + + # Les resultats des semestres taggués à prendre en compte dans le SemX + self.ressembuttags = { + fid: ressembuttags[fid] for fid in semx.semestres_aggreges + } """Les ResSemBUTTags à regrouper dans le SxTag""" # Les données du semestre final @@ -108,7 +117,7 @@ class SxTag(TableTag): """Les etudids triés""" # Affichage - pe_affichage.pe_print(f"--> {self.get_repr()}") + pe_affichage.pe_print(f"--> {self.get_repr(verbose=True)}") # Les tags self.tags_sorted = self.ressembuttag_final.tags_sorted @@ -227,10 +236,10 @@ class SxTag(TableTag): """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle est basée)""" if verbose: - return f"{self.sxtag_id[0]}Tag basé sur {self.rcf.get_repr()}" + return f"SXTag basé sur {self.semx.get_repr()}" else: # affichage = [str(fid) for fid in self.ressembuttags] - return f"{self.sxtag_id[0]}Tag (#{self.fid_final})" + return f"SXTag {self.nom_rcs}#{self.fid_final}" def _aff_capitalisations(self): """Affichage des capitalisations du sxtag pour debug""" diff --git a/app/pe/rcss/pe_rcf.py b/app/pe/rcss/pe_rcf.py deleted file mode 100644 index 494a3505..00000000 --- a/app/pe/rcss/pe_rcf.py +++ /dev/null @@ -1,57 +0,0 @@ - -from app.models import FormSemestre -import app.pe.rcss.pe_rcs as pe_rcs - - -class RCF(pe_rcs.RCS): - """Modélise un ensemble de (form)semestres d'étudiants - associé à un type de regroupement cohérent de semestres - donné (par ex: 'S2', '3S', '2A'). - - Si le RCF est un semestre de type Si, stocke les - formsemestres de numéro i qu'ont suivi l'étudiant pour atteindre le Si - (en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants) - - Pour le RCF de type iS ou iA (par ex, 3A=S1+S2+S3), identifie - les semestres que les étudiants ont suivis pour les amener jusqu'au semestre - terminal du RCS (par ex: ici un S3). - - Ces semestres peuvent être : - - * des S1+S2+S1+S2+S3 si redoublement de la 1ère année - * des S1+S2+(année de césure)+S3 si césure, ... - - Args: - nom_rcs: Un nom du RCS (par ex: '5S') - semestre_final: Le formsemestre final du RCS - """ - - def __init__(self, nom_rcs: str, semestre_final: FormSemestre): - pe_rcs.RCS.__init__(self, nom_rcs, semestre_final) - - self.semestres_aggreges: dict[int:FormSemestre] = {} - """Formsemestres regroupés dans le RCS""" - - def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]): - """Ajout de semestres aux semestres à regrouper - - Args: - semestres: Dictionnaire ``{fid: Formsemestre)`` - """ - 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""" - title = f"""{self.__class__.__name__} {pe_rcs.RCS.__str__(self)}""" - if verbose: - noms = [] - for fid in self.semestres_aggreges: - semestre = self.semestres_aggreges[fid] - noms.append(f"S{semestre.semestre_id}(#{fid})") - noms = sorted(noms) - if noms: - title += " <" + "+".join(noms) + ">" - else: - title += " " - return title diff --git a/app/pe/rcss/pe_rcrcf.py b/app/pe/rcss/pe_rcrcf.py deleted file mode 100644 index f44d9fb3..00000000 --- a/app/pe/rcss/pe_rcrcf.py +++ /dev/null @@ -1,69 +0,0 @@ -############################################################################## -# Module "Avis de poursuite d'étude" -# conçu et développé par Cléo Baras (IUT de Grenoble) -############################################################################## - -""" -Created on 01-2024 - -@author: barasc -""" - -import app.pe.pe_comp as pe_comp -import app.pe.rcss.pe_rcf -import app.pe.rcss.rcss_constantes - -from app.models import FormSemestre -from app.pe import pe_sxtag, pe_affichage -from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date -import app.pe.rcss.pe_rcs as pe_rcs -import app.pe.rcss.pe_rcf as pe_rcf - - - -class RCRCF: - """Modélise les RCF d'étudiants suivis par un étudiant dans - le cadre d'un RCS donné (par ex: 3S=S1+S2+S3). - - Pour rappel : un RCF (par ex. S1) combine les semestres 1 qu'a suivi - l'étudiant pour valider son S1 (1 si étudiant standard, 2 si redoublant). - - Le RCRCF 3S est donc le regroupement du RCF S1 + RCF S2 + RCF S3. - - Il est identifié par le formsemestre de S3 marquant la fin du regroupement. - - - Args: - nom_rcs: Un nom du RCS (par ex: '5S') - semestre_final: Le semestre final du RCS - """ - - def __init__(self, nom_rcs: str, semestre_final: FormSemestre): - pe_rcs.RCS.__init__(self, nom_rcs, semestre_final) - - self.rcfs_aggreges: dict[(str, int) : pe_sxtag.SxTag] = {} - """Les RCFs à aggréger""" - - def add_rcfs_a_aggreger(self, rcfs: dict[(str, int): app.pe.rcss.pe_rcf.RCF]): - """Ajout des RCFs aux RCFS à regrouper - - Args: - rcfs: Dictionnaire ``{(str,fid): RCF}`` à ajouter - """ - self.rcfs_aggreges = self.rcfs_aggreges | rcfs - - def get_repr(self, verbose=True) -> str: - """Représentation textuelle d'un RCSF - basé sur ses RCF aggrégés""" - title = f"""{self.__class__.__name__}{pe_rcs.RCS.__str__(self)}""" - if verbose: - noms = [] - for rcf_id, rcf in self.rcfs_aggreges.items(): - noms.append(rcf.get_repr(verbose=False)) - if noms: - title += " <<" + "+".join(noms) + ">>" - else: - title += " <>" - return title - - diff --git a/app/pe/rcss/pe_rcs.py b/app/pe/rcss/pe_rcs.py index 72a3669b..f9babeee 100644 --- a/app/pe/rcss/pe_rcs.py +++ b/app/pe/rcss/pe_rcs.py @@ -10,32 +10,100 @@ Created on 01-2024 """ from app.models import FormSemestre -import app.pe.rcss.rcss_constantes as rcss_constantes +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 regroupent 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 if not cle.startswith("S")] +TOUS_LES_RCS = list(TYPES_RCS.keys()) +TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")] def get_descr_rcs(nom_rcs: str) -> str: """Renvoie la description pour les tableurs de synthèse Excel d'un nom de RCS""" - return rcss_constantes.TYPES_RCS[nom_rcs]["descr"] + return TYPES_RCS[nom_rcs]["descr"] class RCS: - """Modélise un regroupement cohérent de semestres (formsemestre ou de Sx)""" + """Modélise un regroupement cohérent de semestres, + tous se terminant par un (form)semestre final. + """ def __init__(self, nom_rcs: str, semestre_final: FormSemestre): self.nom: str = nom_rcs """Nom du RCS""" + assert self.nom in TOUS_LES_RCS, "Le nom d'un RCS doit être un aggrégat" self.formsemestre_final: FormSemestre = semestre_final - """FormSemestre terminal du RCS""" + """(Form)Semestre final du RCS""" self.rang_final = self.formsemestre_final.semestre_id - """Le rang du formsemestre final""" + """Rang du formsemestre final""" self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id) """Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)""" + self.fid_final: int = self.formsemestre_final.formsemestre_id + """Identifiant du (Form)Semestre final""" + def get_formsemestre_id_final(self) -> int: """Renvoie l'identifiant du formsemestre final du RCS @@ -58,5 +126,3 @@ class RCS: self.nom == other.nom and self.formsemestre_final == other.formsemestre_final ) - - diff --git a/app/pe/rcss/pe_rcsemx.py b/app/pe/rcss/pe_rcsemx.py new file mode 100644 index 00000000..8c6bb0b0 --- /dev/null +++ b/app/pe/rcss/pe_rcsemx.py @@ -0,0 +1,59 @@ +############################################################################## +# Module "Avis de poursuite d'étude" +# conçu et développé par Cléo Baras (IUT de Grenoble) +############################################################################## + +""" +Created on 01-2024 + +@author: barasc +""" + +from app.models import FormSemestre +from app.pe import pe_sxtag, pe_affichage +from app.pe.rcss import pe_rcs, pe_trajectoires + + +class RCSemX(pe_rcs.RCS): + """Modélise un regroupement cohérent de SemX (en même regroupant + des semestres Sx combinés pour former les résultats des étudiants + au semestre de rang x) dans le but de synthétiser les résultats + du S1 jusqu'au semestre final ciblé par le RCSemX (dépendant de l'aggrégat + visé). + + Par ex: Si l'aggrégat du RCSemX est '3S' (=S1+S2+S3), + regroupement le SemX du S1 + le SemX du S2 + le SemX du S3 (chacun + incluant des infos sur les redoublements). + + Args: + nom_rcs: Un nom du RCS (par ex: '5S') + semestre_final: Le semestre final du RCS + """ + + def __init__(self, nom_rcs: str, semestre_final: FormSemestre): + pe_rcs.RCS.__init__(self, nom_rcs, semestre_final) + + self.semXs_aggreges: dict[(str, int) : pe_sxtag.SxTag] = {} + """Les semX à aggréger""" + + def add_semXs(self, semXs: dict[(str, int) : pe_trajectoires.SemX]): + """Ajoute des semXs aux semXs à regrouper dans le RCSemX + + Args: + semXs: Dictionnaire ``{(str,fid): RCF}`` à ajouter + """ + self.semXs_aggreges = self.semXs_aggreges | semXs + + def get_repr(self, verbose=True) -> str: + """Représentation textuelle d'un RCSF + basé sur ses RCF aggrégés""" + title = f"""{self.__class__.__name__} {pe_rcs.RCS.__str__(self)}""" + if verbose: + noms = [] + for semx_id, semx in self.semXs_aggreges.items(): + noms.append(semx.get_repr(verbose=False)) + if noms: + title += " <<" + "+".join(noms) + ">>" + else: + title += " <>" + return title diff --git a/app/pe/rcss/pe_trajectoires.py b/app/pe/rcss/pe_trajectoires.py new file mode 100644 index 00000000..ed13b17f --- /dev/null +++ b/app/pe/rcss/pe_trajectoires.py @@ -0,0 +1,87 @@ +from app.models import FormSemestre +import app.pe.rcss.pe_rcs as pe_rcs + + +class Trajectoire(pe_rcs.RCS): + """Regroupement Cohérent de Semestres ciblant un type d'aggrégat (par ex. + 'S2', '3S', '1A') et un semestre final, et dont les données regroupées + sont des **FormSemestres** suivis par les étudiants. + + Une *Trajectoire* traduit la succession de semestres + qu'ont pu suivre des étudiants pour aller d'un semestre S1 jusqu'au semestre final + de l'aggrégat. + + Une *Trajectoire* peut être : + + * un RCS de semestre de type "Sx" (cf. classe "SemX"), qui stocke les + formsemestres de rang x qu'ont suivi l'étudiant pour valider le Sx + (en général 1 formsemestre pour les non-redoublants et 2 pour les redoublants) + + * un RCS de type iS ou iA (par ex, 3A=S1+S2+S3), qui identifie + les formsemestres que des étudiants ont suivis pour les amener jusqu'au semestre + terminal du RCS. Par ex: si le RCS est un 3S: + + * des S1+S2+S1+S2+S3 si redoublement de la 1ère année + * des S1+S2+(année de césure)+S3 si césure, ... + + Args: + nom_rcs: Un nom du RCS (par ex: '5S') + semestre_final: Le formsemestre final du RCS + """ + + def __init__(self, nom_rcs: str, semestre_final: FormSemestre): + pe_rcs.RCS.__init__(self, nom_rcs, semestre_final) + + self.semestres_aggreges: dict[int:FormSemestre] = {} + """Formsemestres regroupés dans le RCS""" + + def add_semestres(self, semestres: dict[int:FormSemestre]): + """Ajout de semestres aux semestres à regrouper + + Args: + semestres: Dictionnaire ``{fid: Formsemestre)`` + """ + for sem in semestres.values(): + assert isinstance( + sem, FormSemestre + ), "Les données aggrégées d'une Trajectoire doivent être des FormSemestres" + 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""" + title = f"""{self.__class__.__name__} {pe_rcs.RCS.__str__(self)}""" + if verbose: + noms = [] + for fid in self.semestres_aggreges: + semestre = self.semestres_aggreges[fid] + noms.append(f"S{semestre.semestre_id}#{fid}") + noms = sorted(noms) + if noms: + title += " <" + "+".join(noms) + ">" + else: + title += " " + return title + + +class SemX(Trajectoire): + """Trajectoire (regroupement cohérent de (form)semestres + dans laquelle tous les semestres regroupés sont de même rang `x`. + + Les SemX stocke les + formsemestres de rang x qu'ont suivi l'étudiant pour valider le Sx + (en général 1 formsemestre pour les non-redoublants et 2 pour les redoublants). + + Ils servent à calculer les SemXTag (moyennes par tag des RCS de type `Sx`). + """ + + def __init__(self, trajectoire: Trajectoire): + Trajectoire.__init__(self, trajectoire.nom, trajectoire.formsemestre_final) + + semestres_aggreges = trajectoire.semestres_aggreges + for sem in semestres_aggreges.values(): + assert ( + sem.semestre_id == trajectoire.rang_final + ), "Tous les semestres aggrégés d'un SemX doivent être de même rang" + + self.semestres_aggreges = trajectoire.semestres_aggreges diff --git a/app/pe/rcss/rcss_constantes.py b/app/pe/rcss/rcss_constantes.py deleted file mode 100644 index ee79de27..00000000 --- a/app/pe/rcss/rcss_constantes.py +++ /dev/null @@ -1,64 +0,0 @@ - -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 regroupent 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 if not cle.startswith("S")] -TOUS_LES_RCS = list(TYPES_RCS.keys()) -TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")] \ No newline at end of file