From f4c1d00046624aa761e7bc17037f3b174204eb11 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 8 Feb 2024 22:09:11 +0100 Subject: [PATCH] PE: reformattage code, small bug fix. --- app/pe/pe_affichage.py | 10 +++- app/pe/pe_comp.py | 57 +++++++++--------- app/pe/pe_etudiant.py | 66 ++++++++++----------- app/pe/pe_interclasstag.py | 71 ++++++++++++++++------ app/pe/pe_jury.py | 51 ++++++++-------- app/pe/pe_rcs.py | 118 +++++++++++++++++++++---------------- app/pe/pe_semtag.py | 91 +++++++++++++--------------- app/scodoc/sco_utils.py | 2 +- 8 files changed, 255 insertions(+), 211 deletions(-) diff --git a/app/pe/pe_affichage.py b/app/pe/pe_affichage.py index 48c1e33bb..6dea2f4d0 100644 --- a/app/pe/pe_affichage.py +++ b/app/pe/pe_affichage.py @@ -1,3 +1,11 @@ +############################################################################## +# Module "Avis de poursuite d'étude" +# conçu et développé par Cléo Baras (IUT de Grenoble) +############################################################################## + +"""Affichages, debug +""" + from app import log PE_DEBUG = 0 @@ -15,5 +23,3 @@ else: SANS_NOTE = "-" NOM_STAT_GROUPE = "statistiques du groupe" NOM_STAT_PROMO = "statistiques de la promo" - - diff --git a/app/pe/pe_comp.py b/app/pe/pe_comp.py index 465da1cb9..67805dfa4 100644 --- a/app/pe/pe_comp.py +++ b/app/pe/pe_comp.py @@ -52,7 +52,6 @@ from app.scodoc import sco_formsemestre from app.scodoc.sco_logos import find_logo - # Generated LaTeX files are encoded as: PE_LATEX_ENCODING = "utf-8" @@ -98,7 +97,8 @@ def calcul_age(born: datetime.date) -> int: return today.year - born.year - ((today.month, today.day) < (born.month, born.day)) -def remove_accents(input_unicode_str): +# Nota: scu.suppress_accents fait la même chose mais renvoie un str et non un bytes +def remove_accents(input_unicode_str: str) -> bytes: """Supprime les accents d'une chaine unicode""" nfkd_form = unicodedata.normalize("NFKD", input_unicode_str) only_ascii = nfkd_form.encode("ASCII", "ignore") @@ -133,15 +133,15 @@ def escape_for_latex(s): # ---------------------------------------------------------------------------------------- -def list_directory_filenames(path): - """List of regular filenames in a directory (recursive) +def list_directory_filenames(path: str) -> list[str]: + """List of regular filenames (paths) in a directory (recursive) Excludes files and directories begining with . """ - R = [] + paths = [] for root, dirs, files in os.walk(path, topdown=True): dirs[:] = [d for d in dirs if d[0] != "."] - R += [os.path.join(root, fn) for fn in files if fn[0] != "."] - return R + paths += [os.path.join(root, fn) for fn in files if fn[0] != "."] + return paths def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip): @@ -195,13 +195,15 @@ def add_pe_stuff_to_zip(zipfile, ziproot): def get_annee_diplome_semestre( sem_base: FormSemestre | dict, nbre_sem_formation: int = 6 ) -> int: - """Pour un semestre ``sem_base`` donné (supposé être un semestre d'une formation BUT à 6 semestres) - et connaissant le numéro du semestre, ses dates de début et de fin du semestre, prédit l'année à laquelle - sera remis le diplôme BUT des étudiants qui y sont scolarisés - (en supposant qu'il n'y ait pas de redoublement à venir). + """Pour un semestre ``sem_base`` donné (supposé être un semestre d'une formation BUT + à 6 semestres) et connaissant le numéro du semestre, ses dates de début et de fin du + semestre, prédit l'année à laquelle sera remis le diplôme BUT des étudiants qui y + sont scolarisés (en supposant qu'il n'y ait pas de redoublement à venir). - **Remarque sur le calcul** : Les semestres de 1ère partie d'année (S1, S3, S5 ou S4, S6 pour des semestres décalés) - s'étalent sur deux années civiles ; contrairement au semestre de seconde partie d'année universitaire. + **Remarque sur le calcul** : Les semestres de 1ère partie d'année (S1, S3, S5 ou S4, + S6 pour des semestres décalés) + s'étalent sur deux années civiles ; contrairement au semestre de seconde partie + d'année universitaire. Par exemple : @@ -235,21 +237,22 @@ def get_annee_diplome_semestre( if ( 1 <= sem_id <= nbre_sem_formation ): # Si le semestre est un semestre BUT => problème si formation BUT en 1 an ?? - nbreSemRestant = ( + nb_sem_restants = ( nbre_sem_formation - sem_id ) # nombre de semestres restant avant diplome - nbreAnRestant = nbreSemRestant // 2 # nombre d'annees restant avant diplome - # Flag permettant d'activer ou désactiver un increment à prendre en compte en cas de semestre décalé + nb_annees_restantes = ( + nb_sem_restants // 2 + ) # nombre d'annees restant avant diplome + # Flag permettant d'activer ou désactiver un increment + # à prendre en compte en cas de semestre décalé # avec 1 - delta = 0 si semestre de 1ere partie d'année / 1 sinon delta = annee_fin - annee_debut - decalage = nbreSemRestant % 2 # 0 si S4, 1 si S3, 0 si S2, 1 si S1 + decalage = nb_sem_restants % 2 # 0 si S4, 1 si S3, 0 si S2, 1 si S1 increment = decalage * (1 - delta) - return annee_fin + nbreAnRestant + increment + return annee_fin + nb_annees_restantes + increment -def get_cosemestres_diplomants( - annee_diplome: int -) -> dict[int, FormSemestre]: +def get_cosemestres_diplomants(annee_diplome: int) -> dict[int, FormSemestre]: """Ensemble des cosemestres donnant lieu à diplomation à l'``annee_diplome``. **Définition** : Un co-semestre est un semestre : @@ -264,15 +267,15 @@ def get_cosemestres_diplomants( Returns: Un dictionnaire {fid: FormSemestre(fid)} contenant les cosemestres """ - tousLesSems = ( + tous_les_sems = ( sco_formsemestre.do_formsemestre_list() ) # tous les semestres memorisés dans scodoc cosemestres_fids = { - sem["id"] - for sem in tousLesSems - if get_annee_diplome_semestre(sem) == annee_diplome - } + sem["id"] + for sem in tous_les_sems + if get_annee_diplome_semestre(sem) == annee_diplome + } cosemestres = {} for fid in cosemestres_fids: @@ -281,5 +284,3 @@ def get_cosemestres_diplomants( cosemestres[fid] = cosem return cosemestres - - diff --git a/app/pe/pe_etudiant.py b/app/pe/pe_etudiant.py index 3486e5937..cdf6a5ecd 100644 --- a/app/pe/pe_etudiant.py +++ b/app/pe/pe_etudiant.py @@ -37,13 +37,13 @@ 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 from app.scodoc import sco_utils as scu from app.comp.res_sem import load_formsemestre_results + class EtudiantsJuryPE: """Classe centralisant la gestion des étudiants à prendre en compte dans un jury de PE""" @@ -123,20 +123,15 @@ 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()) - """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""" + # 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""" + # Les identifiants des étudiants ayant redoublés ou ayant abandonnés # Synthèse pe_affichage.pe_print( @@ -279,7 +274,7 @@ class EtudiantsJuryPE: semestres_significatifs = self.get_semestres_significatifs(etudid) # Tri des semestres par numéro de semestre - for i in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT+1): + for i in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1): # les semestres de n°i de l'étudiant: semestres_i = { fid: sem_sig @@ -288,18 +283,20 @@ class EtudiantsJuryPE: } self.cursus[etudid][f"S{i}"] = semestres_i - - - def get_formsemestres_terminaux_aggregat(self, aggregat: str): + def get_formsemestres_terminaux_aggregat( + self, aggregat: str + ) -> dict[int, FormSemestre]: """Pour un aggrégat donné, ensemble des formsemestres terminaux possibles pour l'aggrégat (pour l'aggrégat '3S' incluant S1+S2+S3, a pour semestre terminal S3). Ces formsemestres traduisent : - * les différents parcours des étudiants liés par exemple au choix de modalité (par ex: S1 FI + S2 FI + S3 FI - ou S1 FI + S2 FI + S3 UFA), en renvoyant les formsemestre_id du S3 FI et du S3 UFA. - * les éventuelles situations de redoublement (par ex pour 1 étudiant ayant redoublé sa 2ème année : - S1 + S2 + S3 (1ère session) et S1 + S2 + S3 + S4 + S3 (2ème session), en renvoyant les formsemestre_id du - S3 (1ère session) et du S3 (2ème session) + * les différents parcours des étudiants liés par exemple au choix de modalité + (par ex: S1 FI + S2 FI + S3 FI ou S1 FI + S2 FI + S3 UFA), en renvoyant les + formsemestre_id du S3 FI et du S3 UFA. + * les éventuelles situations de redoublement (par ex pour 1 étudiant ayant + redoublé sa 2ème année : + S1 + S2 + S3 (1ère session) et S1 + S2 + S3 + S4 + S3 (2ème session), en + renvoyant les formsemestre_id du S3 (1ère session) et du S3 (2ème session) Args: aggregat: L'aggrégat @@ -316,7 +313,6 @@ class EtudiantsJuryPE: formsemestres_terminaux[fid] = trajectoire.formsemestre_final return formsemestres_terminaux - def nbre_etapes_max_diplomes(self, etudids: list[int]) -> int: """Partant d'un ensemble d'étudiants, nombre de semestres (étapes) maximum suivis par les étudiants du jury. @@ -407,7 +403,7 @@ def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set: return etudiants_ids -def get_annee_diplome(etud: Identite) -> int: +def get_annee_diplome(etud: Identite) -> int | None: """L'année de diplôme prévue d'un étudiant en fonction de ses semestres d'inscription (pour un BUT). @@ -415,13 +411,14 @@ def get_annee_diplome(etud: Identite) -> int: identite: L'identité d'un étudiant Returns: - L'année prévue de sa diplômation + L'année prévue de sa diplômation, ou None si aucun semestre """ formsemestres_apc = get_semestres_apc(etud) if formsemestres_apc: dates_possibles_diplome = [] - """Années de diplômation prédites en fonction des semestres (d'une formation APC) d'un étudiant""" + # Années de diplômation prédites en fonction des semestres + # (d'une formation APC) d'un étudiant for sem_base in formsemestres_apc: annee = pe_comp.get_annee_diplome_semestre(sem_base) if annee: @@ -452,23 +449,26 @@ def get_semestres_apc(identite: Identite) -> list: def arret_de_formation(etud: Identite, cosemestres: list[FormSemestre]) -> bool: """Détermine si un étudiant a arrêté sa formation. Il peut s'agir : - * d'une réorientation à l'initiative du jury de semestre ou d'une démission (on pourrait - utiliser les code NAR pour réorienté & DEM pour démissionnaire des résultats du jury renseigné dans la BDD, - mais pas nécessaire ici) + * d'une réorientation à l'initiative du jury de semestre ou d'une démission + (on pourrait utiliser les code NAR pour réorienté & DEM pour démissionnaire + des résultats du jury renseigné dans la BDD, mais pas nécessaire ici) - * d'un arrêt volontaire : l'étudiant disparait des listes d'inscrits (sans pour autant avoir été indiqué NAR ou DEM). + * d'un arrêt volontaire : l'étudiant disparait des listes d'inscrits (sans pour + autant avoir été indiqué NAR ou DEM). - Dans les cas, on considérera que l'étudiant a arrêté sa formation s'il n'est pas dans l'un des "derniers" cosemestres - (semestres conduisant à la même année de diplômation) connu dans Scodoc. + Dans les cas, on considérera que l'étudiant a arrêté sa formation s'il n'est pas + dans l'un des "derniers" cosemestres (semestres conduisant à la même année de diplômation) + connu dans Scodoc. - Par ex: au moment du jury PE en fin de S5 (pas de S6 renseigné dans Scodoc), l'étudiant doit appartenir à une - instance des S5 qui conduisent à la diplomation dans l'année visée. S'il n'est que dans un S4, il a sans doute - arrêté. A moins qu'il ne soit parti à l'étranger et là, pas de notes. + Par ex: au moment du jury PE en fin de S5 (pas de S6 renseigné dans Scodoc), + l'étudiant doit appartenir à une instance des S5 qui conduisent à la diplomation dans + l'année visée. S'il n'est que dans un S4, il a sans doute arrêté. A moins qu'il ne soit + parti à l'étranger et là, pas de notes. TODO:: Cas de l'étranger, à coder/tester - **Attention** : Cela suppose que toutes les instances d'un semestre donné (par ex: toutes les instances de S6 - accueillant un étudiant soient créées ; sinon les étudiants non inscrits dans un S6 seront considérés comme - ayant abandonnés) + **Attention** : Cela suppose que toutes les instances d'un semestre donné + (par ex: toutes les instances de S6 accueillant un étudiant soient créées ; sinon les + étudiants non inscrits dans un S6 seront considérés comme ayant abandonnés) TODO:: Peut-être à mettre en regard avec les propositions d'inscriptions d'étudiants dans un nouveau semestre Pour chaque étudiant, recherche son dernier semestre en date (validé ou non) et diff --git a/app/pe/pe_interclasstag.py b/app/pe/pe_interclasstag.py index bd91a9e76..895595edd 100644 --- a/app/pe/pe_interclasstag.py +++ b/app/pe/pe_interclasstag.py @@ -1,16 +1,59 @@ -from app.comp import moy_sem +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +############################################################################## +# Module "Avis de poursuite d'étude" +# conçu et développé par Cléo Baras (IUT de Grenoble) +############################################################################## + +""" +Created on Thu Sep 8 09:36:33 2016 + +@author: barasc +""" + +import pandas as pd +import numpy as np + from app.pe.pe_tabletags import TableTag, MoyenneTag from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_rcs import RCS, RCSsJuryPE from app.pe.pe_rcstag import RCSTag -import pandas as pd -import numpy as np - - class RCSInterclasseTag(TableTag): - # ------------------------------------------------------------------------------------------------------------------- + """ + Interclasse l'ensemble des étudiants diplômés à une année + donnée (celle du jury), pour un RCS donné (par ex: 'S2', '3S') + en reportant : + + * les moyennes obtenues sur la trajectoire qu'il ont suivi pour atteindre + le numéro de semestre de fin de l'aggrégat (indépendamment de son + formsemestre) + * calculant le classement sur les étudiants diplômes + """ + def __init__( self, nom_rcs: str, @@ -18,15 +61,6 @@ class RCSInterclasseTag(TableTag): rcss_jury_pe: RCSsJuryPE, rcss_tags: dict[tuple, RCSTag], ): - """ - Interclasse l'ensemble des étudiants diplômés à une année - donnée (celle du jury), pour un RCS donné (par ex: 'S2', '3S') - en reportant : - - * les moyennes obtenues sur la trajectoire qu'il ont suivi pour atteindre le numéro de semestre de fin de l'aggrégat (indépendamment de son - formsemestres) - * calculant le classement sur les étudiants diplômes - """ TableTag.__init__(self) self.nom_rcs = nom_rcs @@ -43,7 +77,8 @@ class RCSInterclasseTag(TableTag): for etudid in self.diplomes_ids } - # Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat + # Les trajectoires (et leur version tagguées), en ne gardant que + # celles associées à l'aggrégat self.rcss: dict[int, RCS] = {} """Ensemble des trajectoires associées à l'aggrégat""" for trajectoire_id in rcss_jury_pe.rcss: @@ -54,9 +89,7 @@ class RCSInterclasseTag(TableTag): self.trajectoires_taggues: dict[int, RCS] = {} """Ensemble des trajectoires tagguées associées à l'aggrégat""" for trajectoire_id in self.rcss: - self.trajectoires_taggues[trajectoire_id] = rcss_tags[ - trajectoire_id - ] + self.trajectoires_taggues[trajectoire_id] = rcss_tags[trajectoire_id] # Les trajectoires suivies par les étudiants du jury, en ne gardant que # celles associées aux diplomés diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index ba56763a0..ba4740a48 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -47,31 +47,29 @@ import os from zipfile import ZipFile import numpy as np +import pandas as pd from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE - -from app.pe.pe_etudiant import * -from app.pe.pe_rcs import * -import app.pe.pe_comp as pe_comp +import app.pe.pe_affichage as pe_affichage +from app.pe.pe_etudiant import * # TODO A éviter -> pe_etudiant. +from app.pe.pe_rcs import * # TODO A éviter +from app.pe.pe_rcstag import RCSTag from app.pe.pe_semtag import SemestreTag from app.pe.pe_interclasstag import RCSInterclasseTag -from app.pe.pe_rcstag import RCSTag -import app.pe.pe_affichage as pe_affichage - -import pandas as pd class JuryPE(object): - def __init__(self, diplome): - """ - Classe mémorisant toutes les informations nécessaires pour établir un jury de PE, sur la base - d'une année de diplôme. De ce semestre est déduit : - 1. l'année d'obtention du DUT, - 2. tous les étudiants susceptibles à ce stade (au regard de leur parcours) d'être diplomés. + """ + Classe mémorisant toutes les informations nécessaires pour établir un jury de PE, sur la base + d'une année de diplôme. De ce semestre est déduit : + 1. l'année d'obtention du DUT, + 2. tous les étudiants susceptibles à ce stade (au regard de leur parcours) d'être diplomés. - Args: - diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX) - """ + Args: + diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX) + """ + + def __init__(self, diplome): self.diplome = diplome "L'année du diplome" @@ -161,9 +159,7 @@ class JuryPE(object): self.rcss.cree_rcss(self.etudiants) # Génère les moyennes par tags des trajectoires - pe_affichage.pe_print( - "*** Calcule les moyennes par tag des RCS possibles" - ) + pe_affichage.pe_print("*** Calcule les moyennes par tag des RCS possibles") self.rcss_tags = compute_trajectoires_tag( self.rcss, self.etudiants, self.sems_tags ) @@ -381,14 +377,13 @@ class JuryPE(object): champ = (descr, "", "note") notes_traj = moy_traj.get_notes() donnees.loc[etudids_communs, champ] = notes_traj.loc[ - etudids_communs] + etudids_communs + ] # Les rangs champ = (descr, NOM_STAT_GROUPE, "class.") rangs = moy_traj.get_rangs_inscrits() - donnees.loc[etudids_communs, champ] = rangs.loc[ - etudids_communs - ] + donnees.loc[etudids_communs, champ] = rangs.loc[etudids_communs] # Les mins champ = (descr, NOM_STAT_GROUPE, "min") @@ -487,9 +482,7 @@ class JuryPE(object): # La trajectoire de l'étudiant sur l'aggrégat trajectoire = self.rcss.suivi[etudid][aggregat] if trajectoire: - trajectoire_tagguee = self.rcss_tags[ - trajectoire.rcs_id - ] + trajectoire_tagguee = self.rcss_tags[trajectoire.rcs_id] if tag in trajectoire_tagguee.moyennes_tags: # L'interclassement interclass = self.interclassements_taggues[aggregat] @@ -512,6 +505,7 @@ 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 @@ -534,6 +528,7 @@ def get_formsemestres_etudiants(etudiants: EtudiantsJuryPE) -> dict: 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. Chaque semestre taggué est rattaché à l'un des FormSemestre faisant partie du cursus scolaire @@ -548,7 +543,7 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict: Un dictionnaire {fid: SemestreTag(fid)} """ - """Création des semestres taggués, de type 'S1', 'S2', ...""" + # Création des semestres taggués, de type 'S1', 'S2', ... pe_affichage.pe_print("*** Création des semestres taggués") formsemestres = get_formsemestres_etudiants(etudiants) diff --git a/app/pe/pe_rcs.py b/app/pe/pe_rcs.py index ab85511fd..f9ec66883 100644 --- a/app/pe/pe_rcs.py +++ b/app/pe/pe_rcs.py @@ -1,5 +1,15 @@ +############################################################################## +# 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.pe_affichage as pe_affichage from app.models import FormSemestre from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date @@ -59,39 +69,41 @@ TYPES_RCS = { "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""" +"""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.keys() if not cle.startswith("S")] +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.keys() if cle.startswith("S")] +TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")] class RCS: + """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 + """ + 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""" @@ -121,21 +133,22 @@ class RCS: 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}" + title = f"""{self.nom} ({ + self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}""" if verbose and noms: - repr += " - " + "+".join(noms) - return repr + title += " - " + "+".join(noms) + return title class RCSsJuryPE: + """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 + """ + 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""" @@ -155,7 +168,8 @@ class RCSsJuryPE: """ 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)""" + # 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] @@ -164,17 +178,17 @@ class RCSsJuryPE: self.suivi[etudid] = { aggregat: None for aggregat in pe_comp.TOUS_LES_SEMESTRES - + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM + + 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)""" + # 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""" + # 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) @@ -182,21 +196,22 @@ class RCSsJuryPE: else: trajectoire = self.rcss[trajectoire_id] - """La liste des semestres de l'étudiant à prendre en compte - pour cette trajectoire""" + # 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""" + # Ajout des semestres à la trajectoire trajectoire.add_semestres_a_aggreger(semestres_a_aggreger) - """Mémoire la trajectoire suivie par l'étudiant""" + # 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 - ): +def get_rcs_etudiant( + semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str +) -> dict[int, FormSemestre]: """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`. @@ -224,7 +239,7 @@ def get_rcs_etudiant(semestres: dict[int:FormSemestre], formsemestre_final: Form 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): + 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 @@ -247,6 +262,7 @@ def get_rcs_etudiant(semestres: dict[int:FormSemestre], formsemestre_final: Form 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""" diff --git a/app/pe/pe_semtag.py b/app/pe/pe_semtag.py index 01f568a93..9ed2418b6 100644 --- a/app/pe/pe_semtag.py +++ b/app/pe/pe_semtag.py @@ -35,33 +35,30 @@ Created on Fri Sep 9 09:15:05 2016 @author: barasc """ -import numpy as np +import pandas as pd import app.pe.pe_etudiant -from app import db, log, ScoValueError -from app.comp import res_sem, moy_ue, moy_sem -from app.comp.moy_sem import comp_ranks_series -from app.comp.res_compat import NotesTableCompat +from app import db, ScoValueError +from app import comp from app.comp.res_sem import load_formsemestre_results from app.models import FormSemestre from app.models.moduleimpls import ModuleImpl - +import app.pe.pe_affichage as pe_affichage +from app.pe.pe_tabletags import TableTag, MoyenneTag from app.scodoc import sco_tag_module from app.scodoc.codes_cursus import UE_SPORT -import app.pe.pe_affichage as pe_affichage -from app.pe.pe_tabletags import TableTag, TAGS_RESERVES, MoyenneTag -import pandas as pd class SemestreTag(TableTag): + """ + Un SemestreTag représente les résultats des étudiants à un semestre, en donnant + accès aux moyennes par tag. + Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT. + """ + def __init__(self, formsemestre_id: int): """ - Un SemestreTag représente les résultats des étudiants à un semestre, en donnant - accès aux moyennes par tag. - Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT. - Args: - nom: Nom à donner au SemestreTag formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base """ TableTag.__init__(self) @@ -103,27 +100,27 @@ class SemestreTag(TableTag): dict_ues_competences = get_noms_competences_from_ues(self.nt.formsemestre) noms_tags_comp = list(set(dict_ues_competences.values())) noms_tags_auto = ["but"] + noms_tags_comp - self.tags = ( - noms_tags_perso + noms_tags_auto - ) + self.tags = noms_tags_perso + noms_tags_auto """Tags du semestre taggué""" ## Vérifie l'unicité des tags if len(set(self.tags)) != len(self.tags): intersection = list(set(noms_tags_perso) & set(noms_tags_auto)) - liste_intersection = "\n".join([f"
  • {tag}
  • " for tag in intersection]) - message = f"""Erreur dans le module PE : Un des tags saisis dans votre programme de formation - fait parti des tags réservés. En particulier, - votre semestre {self.formsemestre.titre_annee()} - contient le(s) tag(s) réservé(s) suivant : + liste_intersection = "\n".join( + [f"
  • {tag}
  • " for tag in intersection] + ) + s = "s" if len(intersection) > 0 else "" + message = f"""Erreur dans le module PE : Un des tags saisis dans votre + programme de formation fait parti des tags réservés. En particulier, + votre semestre {self.formsemestre.titre_annee()} + contient le{s} tag{s} réservé{s} suivant : - Modifiez votre programme de formation pour le(s) supprimer. Il(s) sera(ont) automatiquement à vos documents de poursuites d'études. + Modifiez votre programme de formation pour le{s} supprimer. + Il{s} ser{'ont' if s else 'a'} automatiquement à vos documents de poursuites d'études. """ - raise ScoValueError( - message - ) + raise ScoValueError(message) # Calcul des moyennes & les classements de chaque étudiant à chaque tag self.moyennes_tags = {} @@ -174,25 +171,25 @@ class SemestreTag(TableTag): La série des moyennes """ - """Adaptation du mask de calcul des moyennes au tag visé""" + # Adaptation du mask de calcul des moyennes au tag visé modimpls_mask = [ modimpl.module.ue.type != UE_SPORT for modimpl in self.formsemestre.modimpls_sorted ] - """Désactive tous les modules qui ne sont pas pris en compte pour ce tag""" + # Désactive tous les modules qui ne sont pas pris en compte pour ce tag for i, modimpl in enumerate(self.formsemestre.modimpls_sorted): if modimpl.moduleimpl_id not in tags_infos[tag]: modimpls_mask[i] = False - """Applique la pondération des coefficients""" + # Applique la pondération des coefficients modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy() for modimpl_id in tags_infos[tag]: ponderation = tags_infos[tag][modimpl_id]["ponderation"] modimpl_coefs_ponderes_df[modimpl_id] *= ponderation - """Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)""" - moyennes_ues_tag = moy_ue.compute_ue_moys_apc( + # Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)# + moyennes_ues_tag = comp.moy_ue.compute_ue_moys_apc( self.sem_cube, self.etuds, self.formsemestre.modimpls_sorted, @@ -203,13 +200,13 @@ class SemestreTag(TableTag): block=self.formsemestre.block_moyennes, ) - """Les ects""" + # Les ects ects = self.ues_inscr_parcours_df.fillna(0.0) * [ ue.ects for ue in self.ues if ue.type != UE_SPORT ] - """Calcule la moyenne générale dans le semestre (pondérée par le ECTS)""" - moy_gen_tag = moy_sem.compute_sem_moys_apc_using_ects( + # Calcule la moyenne générale dans le semestre (pondérée par le ECTS) + moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects( moyennes_ues_tag, ects, formation_id=self.formsemestre.formation_id, @@ -224,11 +221,6 @@ def get_moduleimpl(modimpl_id) -> dict: modimpl = db.session.get(ModuleImpl, modimpl_id) if modimpl: return modimpl - if SemestreTag.DEBUG: - log( - "SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas" - % (modimpl_id) - ) return None @@ -260,27 +252,26 @@ def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre): """ synthese_tags = {} - """Instance des modules du semestre""" + # Instance des modules du semestre modimpls = formsemestre.modimpls_sorted for modimpl in modimpls: modimpl_id = modimpl.id - """Liste des tags pour le module concerné""" + # Liste des tags pour le module concerné tags = sco_tag_module.module_tag_list(modimpl.module.id) - """Traitement des tags recensés, chacun pouvant étant de la forme - "mathématiques", "théorie", "pe:0", "maths:2" - """ + # Traitement des tags recensés, chacun pouvant étant de la forme + # "mathématiques", "théorie", "pe:0", "maths:2" for tag in tags: - """Extraction du nom du tag et du coeff de pondération""" + # Extraction du nom du tag et du coeff de pondération (tagname, ponderation) = sco_tag_module.split_tagname_coeff(tag) - """Ajout d'une clé pour le tag""" + # Ajout d'une clé pour le tag if tagname not in synthese_tags: synthese_tags[tagname] = {} - """Ajout du module (modimpl) au tagname considéré""" + # Ajout du module (modimpl) au tagname considéré synthese_tags[tagname][modimpl_id] = { "modimpl": modimpl, # les données sur le module # "coeff": modimpl.module.coefficient, # le coeff du module dans le semestre @@ -298,6 +289,8 @@ def get_noms_competences_from_ues(formsemestre: FormSemestre) -> dict[int, str]: """Partant d'un formsemestre, extrait le nom des compétences associés à (ou aux) parcours des étudiants du formsemestre. + Ignore les UEs non associées à un niveau de compétence. + Args: formsemestre: Un FormSemestre @@ -310,8 +303,8 @@ def get_noms_competences_from_ues(formsemestre: FormSemestre) -> dict[int, str]: noms_competences = {} for ue in nt.ues: - if ue.type != UE_SPORT: - ordre = ue.niveau_competence.ordre + if ue.niveau_competence and ue.type != UE_SPORT: + # ?? inutilisé ordre = ue.niveau_competence.ordre nom = ue.niveau_competence.competence.titre noms_competences[ue.ue_id] = f"comp. {nom}" return noms_competences diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 52ed9b8ed..64ff0e68e 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -870,7 +870,7 @@ def stripquotes(s): return s -def suppress_accents(s): +def suppress_accents(s: str) -> str: "remove accents and suppress non ascii characters from string s" if isinstance(s, str): return (