From b9b9a172c7fd63388b74fea641c8caadb3c3a84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9o=20BARAS=20=28IUT1=20Grenoble=29?= Date: Tue, 27 Feb 2024 18:00:06 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20de=20l'option=20"G=C3=A9n=C3=A9rer=20le?= =?UTF-8?q?s=20moyennes=20par=20RCUEs=20(comp=C3=A9tences)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/forms/pe/pe_sem_recap.py | 9 +++- app/pe/pe_affichage.py | 25 ++++++++++ app/pe/pe_etudiant.py | 4 ++ app/pe/pe_jury.py | 95 ++++++++++++++++++++++++------------ app/pe/pe_rcss_jury.py | 59 ++++++---------------- 5 files changed, 115 insertions(+), 77 deletions(-) diff --git a/app/forms/pe/pe_sem_recap.py b/app/forms/pe/pe_sem_recap.py index 8789bc217..b85e6e9be 100644 --- a/app/forms/pe/pe_sem_recap.py +++ b/app/forms/pe/pe_sem_recap.py @@ -45,10 +45,15 @@ class ParametrageClasseurPE(FlaskForm): default=True, render_kw={"checked": ""}, ) - moyennes_ues_rcues = BooleanField("Générer les moyennes par RCUEs (compétences)") + moyennes_ues_rcues = BooleanField( + "Générer les moyennes par RCUEs (compétences)", + default=True, + render_kw={"checked": ""}, + ) + min_max_moy = BooleanField("Afficher les colonnes min/max/moy") synthese_individuelle_etud = BooleanField( - "Générer la feuille synthèse avec un onglet par étudiant" + "Générer les synthèses HTML étudiant par étudiant" ) submit = SubmitField("Générer les classeurs poursuites d'études") diff --git a/app/pe/pe_affichage.py b/app/pe/pe_affichage.py index 9eed827f8..4ed7c7d3d 100644 --- a/app/pe/pe_affichage.py +++ b/app/pe/pe_affichage.py @@ -207,3 +207,28 @@ def repr_comp_et_ues(acronymes_ues_to_competences): liste += ["📍" + acro] aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"] return "\n".join(aff_comp) + + +def aff_rcsemxs_suivis_par_etudiants(etudiants): + """Affiche les RCSemX (regroupement de SemX) + amenant un étudiant du S1 à un Sx""" + etudiants_ids = etudiants.etudiants_ids + jeunes = list(enumerate(etudiants_ids)) + + for no_etud, etudid in jeunes: + etat = "⛔" if etudid in etudiants.abandons_ids else "✅" + pe_print(f"-> {etat} {etudiants.identites[etudid].nomprenom} :") + for nom_rcs, rcs in etudiants.rcsemXs[etudid].items(): + if rcs: + 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 etudiants.rcsemXs[etudid][nom_rcs]: + les_rcssemX_suivis.append(etudiants.rcsemXs[etudid][nom_rcs]) + if not les_rcssemX_suivis: + vides += [nom_rcs] + vides = sorted(list(set(vides))) + pe_print(f"⚠️ RCSemX vides : {', '.join(vides)}") diff --git a/app/pe/pe_etudiant.py b/app/pe/pe_etudiant.py index 86f92dd92..7bd65efda 100644 --- a/app/pe/pe_etudiant.py +++ b/app/pe/pe_etudiant.py @@ -69,6 +69,9 @@ class EtudiantsJuryPE: self.semXs: dict[int:dict] = {} """Les semXs (RCS de type Sx) suivis par chaque étudiant""" + self.rcsemXs: dict[int:dict] = {} + """Les RC de SemXs (RCS de type Sx, xA, xS) suivis par chaque étudiant""" + self.etudiants_diplomes = {} """Les identités des étudiants à considérer au jury (ceux qui seront effectivement diplômés)""" @@ -274,6 +277,7 @@ class EtudiantsJuryPE: # Initialise ses trajectoires/SemX/RCSemX self.trajectoires[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS} self.semXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_SEMESTRES} + self.rcsemXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS} def structure_cursus_etudiant(self, etudid: int): """Structure les informations sur les semestres suivis par un diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index b1022c228..440af3f34 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -102,9 +102,8 @@ class JuryPE(object): "Nom du zip où ranger les fichiers générés" # Les options - self.options = options - """Options de configuration""" + """Options de configuration (cf. pe_sem_recap)""" pe_affichage.pe_print( f"Données de poursuite d'étude générées le {time.strftime('%d/%m/%Y à %H:%M')}\n", @@ -350,8 +349,9 @@ class JuryPE(object): pe_affichage.pe_print( """******************************************************************************""" ) - self.rcss_jury.cree_rcsemxs(self.etudiants) - self.rcss_jury._aff_rcsemxs_suivis(self.etudiants) + self.rcss_jury.cree_rcsemxs(options=self.options) + if "moyennes_ues_rcues" in self.options and self.options["moyennes_ues_rcues"]: + pe_affichage.aff_rcsemxs_suivis_par_etudiants(self.etudiants) def _gen_xls_rcstags(self, zipfile: ZipFile): """Génère les RCS taggués traduisant les moyennes (orientées compétences) @@ -384,6 +384,20 @@ class JuryPE(object): ) pe_affichage.pe_print("1) Calcul des moyennes des RCSTag", info=True) + if not self.rcss_jury.rcsemxs: + if ( + "moyennes_ues_rcues" in self.options + and not self.options["moyennes_ues_rcues"] + ): + pe_affichage.pe_print(" -> Pas de RCSemX à calculer (cf. options)") + else: + pe_affichage.pe_print( + " -> Pas de RCSemX à calculer (alors qu'aucune option ne les limite) => problème" + ) + self.rcsstags = {} + return + + # Calcul des RCSTags sur la base des RCSemX self.rcsstags = {} for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items(): self.rcsstags[rcs_id] = pe_rcstag.RCSemXTag( @@ -431,20 +445,26 @@ class JuryPE(object): """******************************************************************""" ) pe_affichage.pe_print( - """*** Génère les interclassements sur chaque type de RCS/agrgégat""" + """*** Génère les interclassements sur chaque type de RCS/agrégat""" ) pe_affichage.pe_print( """******************************************************************""" ) - self.interclasstags = { - pe_moytag.CODE_MOY_UE: {}, - pe_moytag.CODE_MOY_COMPETENCES: {}, - } + if ( + "moyennes_ues_rcues" not in self.options + or self.options["moyennes_ues_rcues"] + ): + self.interclasstags = { + pe_moytag.CODE_MOY_UE: {}, + pe_moytag.CODE_MOY_COMPETENCES: {}, + } + else: + self.interclasstags = {pe_moytag.CODE_MOY_UE: {}} etudiants_diplomes = self.etudiants.etudiants_diplomes - # Les interclassements par UE + # Les interclassements par UE (toujours présents par défaut) for Sx in pe_rcs.TOUS_LES_SEMESTRES: interclass = pe_interclasstag.InterClassTag( Sx, @@ -457,16 +477,22 @@ class JuryPE(object): self.interclasstags[pe_moytag.CODE_MOY_UE][Sx] = interclass # Les interclassements par compétences - for nom_rcs in pe_rcs.TOUS_LES_RCS: - interclass = pe_interclasstag.InterClassTag( - nom_rcs, - pe_moytag.CODE_MOY_COMPETENCES, - etudiants_diplomes, - self.rcss_jury.rcsemxs, - self.rcsstags, - self.rcss_jury.rcsemxs_suivis, - ) - self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][nom_rcs] = interclass + if ( + "moyennes_ues_rcues" not in self.options + or self.options["moyennes_ues_rcues"] + ): + for nom_rcs in pe_rcs.TOUS_LES_RCS: + interclass = pe_interclasstag.InterClassTag( + nom_rcs, + pe_moytag.CODE_MOY_COMPETENCES, + etudiants_diplomes, + self.rcss_jury.rcsemxs, + self.rcsstags, + self.rcss_jury.rcsemxs_suivis, + ) + self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][ + nom_rcs + ] = interclass # Intègre le bilan des aggrégats (interclassé par promo) au zip final output = io.BytesIO() @@ -474,10 +500,9 @@ class JuryPE(object): output, engine="openpyxl" ) as writer: onglets = [] - for type_interclass in [ - pe_moytag.CODE_MOY_UE, - pe_moytag.CODE_MOY_COMPETENCES, - ]: + for ( + type_interclass + ) in self.interclasstags: # Pour les types d'interclassements prévus interclasstag = self.interclasstags[type_interclass] for nom_rcs, interclass in interclasstag.items(): if interclass.is_significatif(): @@ -522,7 +547,7 @@ class JuryPE(object): tags = self._do_tags_list(self.interclasstags) for tag in tags: - for type_moy in [pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_COMPETENCES]: + for type_moy in self.interclasstags: self.synthese[(tag, type_moy)] = self.df_tag_type(tag, type_moy) # Export des données => mode 1 seule feuille -> supprimé @@ -555,7 +580,7 @@ class JuryPE(object): if onglets: self.add_file_to_zip( - zipfile, f"synthese_jury_{self.diplome}_par_tag.xlsx", output.read() + zipfile, f"synthese_jury_{self.diplome}.xlsx", output.read() ) def _gen_html_synthese_par_etudiant(self, zipfile: ZipFile): @@ -565,12 +590,18 @@ class JuryPE(object): pe_affichage.pe_print("*** Synthèse finale étudiant par étudiant", info=True) pe_affichage.pe_print("**************************************************") - etudids = list(self.diplomes_ids) - for etudid in etudids: - nom, prenom, html = self.synthetise_jury_etudiant(etudid) - self.add_file_to_zip( - zipfile, f"{nom}_{prenom}.html", html, path="etudiants" - ) + if ( + "moyennes_ues_rcues" not in self.options + or self.options["moyennes_ues_rcues"] + ): + etudids = list(self.diplomes_ids) + for etudid in etudids: + nom, prenom, html = self.synthetise_jury_etudiant(etudid) + self.add_file_to_zip( + zipfile, f"{nom}_{prenom}.html", html, path="etudiants" + ) + else: + pe_affichage.pe_print(" > Pas de synthèse étudiant/étudiant possible/prévu") def _add_log_to_zip(self, zipfile): """Add a text file with the log messages""" diff --git a/app/pe/pe_rcss_jury.py b/app/pe/pe_rcss_jury.py index 99003c2fd..db61e3a16 100644 --- a/app/pe/pe_rcss_jury.py +++ b/app/pe/pe_rcss_jury.py @@ -117,7 +117,7 @@ class RCSsJuryPE: self.semXs_suivis[etudid][agregat] = semX self.etudiants.semXs[etudid][agregat] = semX - def cree_rcsemxs(self, etudiants: pe_etudiant.EtudiantsJuryPE): + 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 @@ -125,6 +125,11 @@ class RCSsJuryPE: 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: @@ -132,20 +137,11 @@ class RCSsJuryPE: 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] + # 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][nom_rcs] = None + self.rcsemxs_suivis[etudid][agregat] = None else: # Identifiant de la trajectoire => donnera ceux du RCSemX tid = trajectoire.rcs_id @@ -159,14 +155,14 @@ class RCSsJuryPE: # 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"] + noms_sems_aggregat = pe_rcs.TYPES_RCS[agregat]["aggregat"] semxs_a_aggreger = {} for Sx in noms_sems_aggregat: - semestres_etudiants = etudiants.cursus[etudid][Sx] + semestres_etudiants = self.etudiants.cursus[etudid][Sx] if not semestres_etudiants: pas_de_semestres += [ - f"{Sx} pour {etudiants.identites[etudid].nomprenom}" + f"{Sx} pour {self.etudiants.identites[etudid].nomprenom}" ] else: semx_id = get_semx_from_semestres_aggreges( @@ -184,9 +180,10 @@ class RCSsJuryPE: rcsemx.add_semXs(semxs_a_aggreger) # Mémoire du RCSemX aux informations de suivi de l'étudiant - self.rcsemxs_suivis[etudid][nom_rcs] = rcsemx + self.rcsemxs_suivis[etudid][agregat] = rcsemx + self.etudiants.rcsemXs[etudid][agregat] = rcsemx - # Affichage des étudiants pour lesquels il manque un semestre + # 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 :") @@ -194,30 +191,6 @@ class RCSsJuryPE: "\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