diff --git a/app/pe/pe_affichage.py b/app/pe/pe_affichage.py new file mode 100644 index 00000000..484ae5da --- /dev/null +++ b/app/pe/pe_affichage.py @@ -0,0 +1,68 @@ +from app.models import Formation, FormSemestre +from app.scodoc import codes_cursus + + +def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str: + """Nom d'un semestre à afficher dans le descriptif des étapes de la scolarité + d'un étudiant. + + Par ex: Pour un S2, affiche ``"Semestre 2 FI S014-2015 (129)"`` avec : + + * 2 le numéro du semestre, + * FI la modalité, + * 2014-2015 les dates + + Args: + semestre: Un ``FormSemestre`` + avec_fid: Ajoute le n° du semestre à la description + + Returns: + La chaine de caractères décrivant succintement le semestre + """ + formation: Formation = semestre.formation + parcours = codes_cursus.get_cursus_from_code(formation.type_parcours) + + description = [ + parcours.SESSION_NAME.capitalize(), + str(semestre.semestre_id), + semestre.modalite, # eg FI ou FC + f"{semestre.date_debut.year}-{semestre.date_fin.year}", + ] + if avec_fid: + description.append(f"({semestre.forsemestre_id})") + + return " ".join(description) + + +def etapes_du_cursus(semestres: dict[int, FormSemestre], nbre_etapes_max: int) -> list[str]: + """Partant d'un dictionnaire de semestres (qui retrace + la scolarité d'un étudiant), liste les noms des + semestres (en version abbrégée) + qu'un étudiant a suivi au cours de sa scolarité à l'IUT. + Les noms des semestres sont renvoyés dans un dictionnaire + ``{"etape i": nom_semestre_a_etape_i}`` + avec i variant jusqu'à nbre_semestres_max. (S'il n'y a pas de semestre à l'étape i, + le nom affiché est vide. + + La fonction suppose la liste des semestres triées par ordre + décroissant de date. + + Args: + semestres: une liste de ``FormSemestre`` + nbre_etapes_max: le nombre d'étapes max prise en compte + + Returns: + Une liste de nom de semestre (dans le même ordre que les ``semestres``) + + See also: + app.pe.pe_affichage.nom_semestre_etape + """ + assert len(semestres) <= nbre_etapes_max + + noms = [nom_semestre_etape(sem, avec_fid=False) for (fid, sem) in semestres.items()] + noms = noms[::-1] # trie par ordre croissant + + dico = {f"Etape {i+1}": "" for i in range(nbre_etapes_max)} + for (i, nom) in enumerate(noms): # Charge les noms de semestres + dico[f"Etape {i+1}"] = nom + return dico diff --git a/app/pe/pe_comp.py b/app/pe/pe_comp.py new file mode 100644 index 00000000..e57dfa4d --- /dev/null +++ b/app/pe/pe_comp.py @@ -0,0 +1,384 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# 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 os +import datetime +import re +import unicodedata + + +from flask import g + +import app.scodoc.sco_utils as scu +from app import log +from app.models import FormSemestre +from app.scodoc import sco_formsemestre +from app.scodoc.sco_logos import find_logo + +PE_DEBUG = 1 + +if not PE_DEBUG: + # log to notes.log + def pe_print(*a, **kw): + # kw is ignored. log always add a newline + log(" ".join(a)) + +else: + pe_print = print # print function + + +# Generated LaTeX files are encoded as: +PE_LATEX_ENCODING = "utf-8" + +# /opt/scodoc/tools/doc_poursuites_etudes +REP_DEFAULT_AVIS = os.path.join(scu.SCO_TOOLS_DIR, "doc_poursuites_etudes/") +REP_LOCAL_AVIS = os.path.join(scu.SCODOC_CFG_DIR, "doc_poursuites_etudes/") + +PE_DEFAULT_AVIS_LATEX_TMPL = REP_DEFAULT_AVIS + "distrib/modeles/un_avis.tex" +PE_LOCAL_AVIS_LATEX_TMPL = REP_LOCAL_AVIS + "local/modeles/un_avis.tex" +PE_DEFAULT_FOOTER_TMPL = REP_DEFAULT_AVIS + "distrib/modeles/un_footer.tex" +PE_LOCAL_FOOTER_TMPL = REP_LOCAL_AVIS + "local/modeles/un_footer.tex" + +# ---------------------------------------------------------------------------------------- + +""" +Descriptif d'un parcours classique BUT + +TODO:: A améliorer si BUT en moins de 6 semestres +""" + +PARCOURS = { + "S1": { + "aggregat": ["S1"], + "ordre": 1, + "affichage_court": "S1", + "affichage_long": "Semestre 1", + }, + "S2": { + "aggregat": ["S2"], + "ordre": 2, + "affichage_court": "S2", + "affichage_long": "Semestre 2", + }, + "1A": { + "aggregat": ["S1", "S2"], + "ordre": 3, + "affichage_court": "1A", + "affichage_long": "1ère année", + }, + "S3": { + "aggregat": ["S3"], + "ordre": 4, + "affichage_court": "S3", + "affichage_long": "Semestre 3", + }, + "S4": { + "aggregat": ["S4"], + "ordre": 5, + "affichage_court": "S4", + "affichage_long": "Semestre 4", + }, + "2A": { + "aggregat": ["S3", "S4"], + "ordre": 6, + "affichage_court": "2A", + "affichage_long": "2ème année", + }, + "3S": { + "aggregat": ["S1", "S2", "S3"], + "ordre": 7, + "affichage_court": "S1+S2+S3", + "affichage_long": "BUT du semestre 1 au semestre 3", + }, + "4S": { + "aggregat": ["S1", "S2", "S3", "S4"], + "ordre": 8, + "affichage_court": "BUT", + "affichage_long": "BUT du semestre 1 au semestre 4", + }, + "S5": { + "aggregat": ["S5"], + "ordre": 9, + "affichage_court": "S5", + "affichage_long": "Semestre 5", + }, + "S6": { + "aggregat": ["S6"], + "ordre": 10, + "affichage_court": "S6", + "affichage_long": "Semestre 6", + }, + "3A": { + "aggregat": ["S5", "S6"], + "ordre": 11, + "affichage_court": "3A", + "affichage_long": "3ème année", + }, + "5S": { + "aggregat": ["S1", "S2", "S3", "S4", "S5"], + "ordre": 12, + "affichage_court": "S1+S2+S3+S4+S5", + "affichage_long": "BUT du semestre 1 au semestre 5", + }, + "6S": { + "aggregat": ["S1", "S2", "S3", "S4", "S5", "S6"], + "ordre": 13, + "affichage_court": "BUT", + "affichage_long": "BUT (tout semestre inclus)", + }, +} +NBRE_SEMESTRES_DIPLOMANT = 6 +AGGREGAT_DIPLOMANT = ( + "6S" # aggrégat correspondant à la totalité des notes pour le diplôme +) +TOUS_LES_SEMESTRES = PARCOURS[AGGREGAT_DIPLOMANT]["aggregat"] +TOUS_LES_AGGREGATS = [cle for cle in PARCOURS.keys() if not cle.startswith("S")] +TOUS_LES_PARCOURS = list(PARCOURS.keys()) + +# ---------------------------------------------------------------------------------------- +def calcul_age(born: datetime.date) -> int: + """Calcule l'age connaissant la date de naissance ``born``. (L'age est calculé + à partir de l'horloge système). + + Args: + born: La date de naissance + + Return: + L'age (au regard de la date actuelle) + """ + if not born or not isinstance(born, datetime.date): + return None + + today = datetime.date.today() + return ( + today.year + - born.year + - ((today.month, today.day) < (born.month, born.day)) + ) + + +def remove_accents(input_unicode_str): + """Supprime les accents d'une chaine unicode""" + nfkd_form = unicodedata.normalize("NFKD", input_unicode_str) + only_ascii = nfkd_form.encode("ASCII", "ignore") + return only_ascii + + +def escape_for_latex(s): + """Protège les caractères pour inclusion dans du source LaTeX""" + if not s: + return "" + conv = { + "&": r"\&", + "%": r"\%", + "$": r"\$", + "#": r"\#", + "_": r"\_", + "{": r"\{", + "}": r"\}", + "~": r"\textasciitilde{}", + "^": r"\^{}", + "\\": r"\textbackslash{}", + "<": r"\textless ", + ">": r"\textgreater ", + } + exp = re.compile( + "|".join( + re.escape(key) + for key in sorted(list(conv.keys()), key=lambda item: -len(item)) + ) + ) + return exp.sub(lambda match: conv[match.group()], s) + + +# ---------------------------------------------------------------------------------------- +def list_directory_filenames(path): + """List of regular filenames in a directory (recursive) + Excludes files and directories begining with . + """ + R = [] + 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 + + +def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip): + """Read pathname server file and add content to zip under path_in_zip""" + rooted_path_in_zip = os.path.join(ziproot, path_in_zip) + zipfile.write(filename=pathname, arcname=rooted_path_in_zip) + # data = open(pathname).read() + # zipfile.writestr(rooted_path_in_zip, data) + + +def add_refs_to_register(register, directory): + """Ajoute les fichiers trouvés dans directory au registre (dictionaire) sous la forme + filename => pathname + """ + length = len(directory) + for pathname in list_directory_filenames(directory): + filename = pathname[length + 1 :] + register[filename] = pathname + + +def add_pe_stuff_to_zip(zipfile, ziproot): + """Add auxiliary files to (already opened) zip + Put all local files found under config/doc_poursuites_etudes/local + and config/doc_poursuites_etudes/distrib + If a file is present in both subtrees, take the one in local. + + Also copy logos + """ + register = {} + # first add standard (distrib references) + distrib_dir = os.path.join(REP_DEFAULT_AVIS, "distrib") + add_refs_to_register(register=register, directory=distrib_dir) + # then add local references (some oh them may overwrite distrib refs) + local_dir = os.path.join(REP_LOCAL_AVIS, "local") + add_refs_to_register(register=register, directory=local_dir) + # at this point register contains all refs (filename, pathname) to be saved + for filename, pathname in register.items(): + add_local_file_to_zip(zipfile, ziproot, pathname, "avis/" + filename) + + # Logos: (add to logos/ directory in zip) + logos_names = ["header", "footer"] + for name in logos_names: + logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id) + if logo is not None: + add_local_file_to_zip( + zipfile, ziproot, logo.filepath, "avis/logos/" + logo.filename + ) + + +# ---------------------------------------------------------------------------------------- +def get_annee_diplome_semestre(sem_base, nbre_sem_formation=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). + + **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 : + + * S5 débutant en 2025 finissant en 2026 : diplome en 2026 + * S3 debutant en 2025 et finissant en 2026 : diplome en 2027 + + La fonction est adaptée au cas des semestres décalés. + + Par exemple : + + * S5 décalé débutant en 2025 et finissant en 2025 : diplome en 2026 + * S3 décalé débutant en 2025 et finissant en 2025 : diplome en 2027 + + Args: + sem_base: Le semestre à partir duquel est prédit l'année de diplomation, soit : + + * un ``FormSemestre`` (Scodoc9) + * un dict (format compatible avec Scodoc7) + + nbre_sem_formation: Le nombre de semestre prévu dans la formation (par défaut 6 pour un BUT) + """ + + if isinstance(sem_base, FormSemestre): + sem_id = sem_base.semestre_id + annee_fin = sem_base.date_fin.year + annee_debut = sem_base.date_debut.year + else: # sem_base est un dictionnaire (Scodoc 7) + sem_id = sem_base["semestre_id"] + annee_fin = int(sem_base["annee_fin"]) + annee_debut = int(sem_base["annee_debut"]) + if ( + 1 <= sem_id <= nbre_sem_formation + ): # Si le semestre est un semestre BUT => problème si formation BUT en 1 an ?? + nbreSemRestant = ( + 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é + # 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 + increment = decalage * (1 - delta) + return annee_fin + nbreAnRestant + increment + + +def get_cosemestres_diplomants(annee_diplome: int, formation_id: int) -> list: + """Ensemble des cosemestres donnant lieu à diplomation à l'``annee_diplome`` + et s'intégrant à la formation donnée par son ``formation_id``. + + **Définition** : Un co-semestre est un semestre : + + * dont l'année de diplômation prédite (sans redoublement) est la même + * dont la formation est la même (optionnel) + * qui a des étudiants inscrits + + Si formation_id == None, ne prend pas en compte l'identifiant de formation + TODO:: A raccrocher à un programme + + Args: + annee_diplome: L'année de diplomation + formation_id: L'identifiant de la formation + """ + tousLesSems = ( + sco_formsemestre.do_formsemestre_list() + ) # tous les semestres memorisés dans scodoc + + if formation_id: + cosemestres_fids = { + sem["id"] + for sem in tousLesSems + if get_annee_diplome_semestre(sem) == annee_diplome + and sem["formation_id"] == formation_id + } + else: + cosemestres_fids = { + sem["id"] + for sem in tousLesSems + if get_annee_diplome_semestre(sem) == annee_diplome + } + + cosemestres = {} + for fid in cosemestres_fids: + cosem = FormSemestre.get_formsemestre(fid) + if len(cosem.etuds_inscriptions) > 0: + cosemestres[fid] = cosem + + return cosemestres + diff --git a/app/pe/pe_etudiant.py b/app/pe/pe_etudiant.py index fdbbc72b..26edc720 100644 --- a/app/pe/pe_etudiant.py +++ b/app/pe/pe_etudiant.py @@ -36,9 +36,10 @@ Created on 17/01/2024 @author: barasc """ -import app.pe.pe_tools as pe_tools +import app.pe.pe_comp as pe_comp from app.models import FormSemestre, Identite -from app.pe.pe_tools import pe_print +from app.pe.pe_comp import pe_print + class EtudiantsJuryPE: """Classe centralisant la gestion des étudiants à prendre en compte dans un jury de PE""" @@ -79,22 +80,22 @@ class EtudiantsJuryPE: *Remarque* : ex: JuryPE.get_etudiants_in_jury() """ "Les cosemestres donnant lieu à même année de diplome" - cosemestres = pe_tools.get_cosemestres_diplomants(self.annee_diplome, None) + cosemestres = pe_comp.get_cosemestres_diplomants(self.annee_diplome, None) self.cosemestres = cosemestres - pe_tools.pe_print( + pe_comp.pe_print( "1) Recherche des coSemestres -> %d trouvés" % len(cosemestres) ) """Les étudiants inscrits dans les co-semestres (ceux du jury mais aussi d'autres ayant été réorientés ou ayant abandonnés)""" - pe_tools.pe_print("2) Liste des étudiants dans les différents co-semestres") + pe_comp.pe_print("2) Liste des étudiants dans les différents co-semestres") self.etudiants_ids = get_etudiants_dans_semestres(cosemestres) - pe_tools.pe_print( + pe_comp.pe_print( " => %d étudiants trouvés dans les cosemestres" % len(self.etudiants_ids) ) """Analyse des parcours étudiants pour déterminer leur année effective de diplome avec prise en compte des redoublements, des abandons, ....""" - pe_tools.pe_print("3) Analyse des parcours individuels des étudiants") + pe_comp.pe_print("3) Analyse des parcours individuels des étudiants") no_etud = 0 for no_etud, etudid in enumerate(self.etudiants_ids): @@ -109,9 +110,9 @@ class EtudiantsJuryPE: self.structure_cursus_etudiant(etudid) if (no_etud + 1) % 10 == 0: - pe_tools.pe_print((no_etud + 1), " ", end="") + pe_comp.pe_print((no_etud + 1), " ", end="") no_etud += 1 - pe_tools.pe_print() + pe_comp.pe_print() """Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris""" self.etudiants_diplomes = self.get_etudiants_diplomes() @@ -124,19 +125,19 @@ class EtudiantsJuryPE: self.formsemestres_jury_ids = self.get_formsemestres() # Synthèse - pe_tools.pe_print( + pe_comp.pe_print( f" => {len(self.etudiants_diplomes)} étudiants à diplômer en {self.annee_diplome}" ) nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_diplomes) - pe_tools.pe_print(f" => {nbre_abandons} étudiants éliminer pour abandon") - pe_tools.pe_print( + pe_comp.pe_print(f" => {nbre_abandons} étudiants éliminer pour abandon") + pe_comp.pe_print( f" => {len(self.formsemestres_jury_ids)} semestres dont il faut calculer la moyenne" ) - pe_tools.pe_print( + pe_comp.pe_print( f" => quelques étudiants futurs diplômés : " + ", ".join([str(etudid) for etudid in list(self.etudiants_diplomes)[:10]]) ) - pe_tools.pe_print( + pe_comp.pe_print( f" => semestres dont il faut calculer les moyennes : " + ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)]) ) @@ -183,9 +184,11 @@ class EtudiantsJuryPE: identite = Identite.get_etud(etudid) """Le cursus global de l'étudiant (restreint aux semestres APC)""" + formsemestres = identite.get_formsemestres() + semestres_etudiant = { frmsem.formsemestre_id: frmsem - for frmsem in identite.get_formsemestres() + for frmsem in formsemestres if frmsem.formation.is_apc() } @@ -193,8 +196,10 @@ class EtudiantsJuryPE: "etudid": etudid, # les infos sur l'étudiant "etat_civil": identite.etat_civil, # Ajout à la table jury "nom": identite.nom, + "entree": formsemestres[-1].date_debut.year, # La date d'entrée à l'IUT "diplome": annee_diplome(identite), # Le date prévisionnelle de son diplôme "formsemestres": semestres_etudiant, # les semestres de l'étudiant + "nb_semestres": len(semestres_etudiant), # le nombre de semestres de l'étudiant "abandon": False, # va être traité en dessous } @@ -218,7 +223,7 @@ class EtudiantsJuryPE: semestres_significatifs = {} for fid in semestres_etudiant: semestre = semestres_etudiant[fid] - if pe_tools.get_annee_diplome_semestre(semestre) <= self.annee_diplome: + if pe_comp.get_annee_diplome_semestre(semestre) <= self.annee_diplome: semestres_significatifs[fid] = semestre return semestres_significatifs @@ -234,7 +239,7 @@ class EtudiantsJuryPE: semestres_significatifs = self.get_semestres_significatifs(etudid) """Tri des semestres par numéro de semestre""" - for nom_sem in pe_tools.TOUS_LES_SEMESTRES: + for nom_sem in pe_comp.TOUS_LES_SEMESTRES: i = int(nom_sem[1]) # le n° du semestre semestres_i = { fid: semestres_significatifs[fid] @@ -324,7 +329,7 @@ class EtudiantsJuryPE: """ if semestres_recherches is None: """Appel récursif pour obtenir tous les semestres (validants)""" - semestres = self.get_formsemestres(pe_tools.AGGREGAT_DIPLOMANT) + semestres = self.get_formsemestres(pe_comp.AGGREGAT_DIPLOMANT) return semestres elif isinstance(semestres_recherches, list): """Appel récursif sur tous les éléments de la liste""" @@ -335,16 +340,16 @@ class EtudiantsJuryPE: return semestres elif ( isinstance(semestres_recherches, str) - and semestres_recherches in pe_tools.TOUS_LES_AGGREGATS + and semestres_recherches in pe_comp.TOUS_LES_AGGREGATS ): """Cas d'un aggrégat avec appel récursif sur toutes les entrées de l'aggrégat""" semestres = self.get_formsemestres( - pe_tools.PARCOURS[semestres_recherches]["aggregat"] + pe_comp.PARCOURS[semestres_recherches]["aggregat"] ) return semestres elif ( isinstance(semestres_recherches, str) - and semestres_recherches in pe_tools.TOUS_LES_SEMESTRES + and semestres_recherches in pe_comp.TOUS_LES_SEMESTRES ): """semestres_recherches est un nom de semestre de type S1, pour une recherche parmi les étudiants à prendre en compte @@ -359,6 +364,15 @@ class EtudiantsJuryPE: else: raise ValueError("Probleme de paramètres d'appel dans get_formsemestreids") + def nbre_etapes_max_diplomes(self): + """Connaissant les étudiants diplomes du jury PE, + nombre de semestres (étapes) maximum suivis par les étudiants du jury. + """ + nbres_semestres = [] + for etudid in self.diplomes_ids: + nbres_semestres.append( self.cursus[etudid]["nb_semestres"] ) + return max(nbres_semestres) + def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set: """Ensemble d'identifiants des étudiants (identifiés via leur ``etudid``) @@ -402,7 +416,7 @@ def annee_diplome(identite: Identite) -> int: if formsemestres: return max( [ - pe_tools.get_annee_diplome_semestre(sem_base) + pe_comp.get_annee_diplome_semestre(sem_base) for sem_base in formsemestres ] ) @@ -459,14 +473,14 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b # semestre impair => passage de droit en semestre pair suivant (effet de l'annualisation) if numero_dernier_formsemestre % 2 == 1: numeros_possibles = list( - range(numero_dernier_formsemestre + 1, pe_tools.NBRE_SEMESTRES_DIPLOMANT) + range(numero_dernier_formsemestre + 1, pe_comp.NBRE_SEMESTRES_DIPLOMANT) ) # semestre pair => passage en année supérieure ou redoublement else: # numeros_possibles = list( range( max(numero_dernier_formsemestre - 1, 1), - pe_tools.NBRE_SEMESTRES_DIPLOMANT, + pe_comp.NBRE_SEMESTRES_DIPLOMANT, ) ) @@ -486,9 +500,11 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b return False -def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]): + + +def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSemestre: """Renvoie le dernier semestre en **date de fin** d'un dictionnaire - de semestres de la forme ``{fid: FormSemestre(fid)}``. + de semestres (potentiellement non trié) de la forme ``{fid: FormSemestre(fid)}``. Args: semestres: Un dictionnaire de semestres @@ -505,3 +521,5 @@ def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]): return dernier_semestre else: return None + + diff --git a/app/pe/pe_interclasstag.py b/app/pe/pe_interclasstag.py index f20dbf24..8fc4727b 100644 --- a/app/pe/pe_interclasstag.py +++ b/app/pe/pe_interclasstag.py @@ -1,8 +1,5 @@ -import pandas as pd -from app.pe import pe_tagtable -from app.pe.pe_tools import PE_DEBUG, pe_print -import app.pe.pe_etudiant as pe_etudiant +from app.pe.pe_tabletags import TableTag from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE from app.pe.pe_trajectoiretag import TrajectoireTag @@ -12,7 +9,7 @@ import pandas as pd import numpy as np -class AggregatInterclasseTag(pe_tagtable.TableTag): +class AggregatInterclasseTag(TableTag): """Interclasse l'ensemble des étudiants diplômés à une année donnée (celle du jury), pour un aggrégat donné (par ex: 'S2', '3S') en reportant : @@ -32,7 +29,7 @@ class AggregatInterclasseTag(pe_tagtable.TableTag): ): """""" """Table nommée au nom de l'aggrégat (par ex: 3S""" - pe_tagtable.TableTag.__init__(self, nom_aggregat) + TableTag.__init__(self, nom_aggregat) """Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)""" self.diplomes_ids = etudiants.etudiants_diplomes diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py new file mode 100644 index 00000000..1f858504 --- /dev/null +++ b/app/pe/pe_jury.py @@ -0,0 +1,477 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# 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 Fri Sep 9 09:15:05 2016 + +@author: barasc +""" + +# ---------------------------------------------------------- +# Ensemble des fonctions et des classes +# permettant les calculs preliminaires (hors affichage) +# a l'edition d'un jury de poursuites d'etudes +# ---------------------------------------------------------- + +import io +import os +from zipfile import ZipFile + + +from app.comp import res_sem +from app.comp.res_compat import NotesTableCompat +from app.models import FormSemestre +from app.models.etudiants import Identite + +from app.scodoc.gen_tables import GenTable, SeqGenTable +import app.scodoc.sco_utils as scu +from app.pe.pe_etudiant import EtudiantsJuryPE +from app.pe.pe_trajectoire import TrajectoiresJuryPE, Trajectoire +import app.pe.pe_comp as pe_comp +from app.pe.pe_semtag import SemestreTag +from app.pe.pe_interclasstag import AggregatInterclasseTag +from app.pe.pe_trajectoiretag import TrajectoireTag +import app.pe.pe_affichage as pe_affichage + +import pandas as pd +import numpy as np + +# ---------------------------------------------------------------------------------------- + + +# ---------------------------------------------------------------------------------------- +class JuryPE(object): + """Classe mémorisant toutes les informations nécessaires pour établir un jury de PE. + Modèle basé sur NotesTable. + + Attributs : + + * diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX) + * juryEtudDict : dictionnaire récapitulant les étudiants participant au jury PE (données administratives + + celles des semestres valides à prendre en compte permettant le calcul des moyennes ... + ``{'etudid : { 'nom', 'prenom', 'civilite', 'diplome', '', }}`` + + Rq: il contient à la fois les étudiants qui vont être diplomés à la date prévue + et ceux qui sont éliminés (abandon, redoublement, ...) pour affichage alternatif + """ + + # Variables de classe décrivant les aggrégats, leur ordre d'apparition temporelle et + # leur affichage dans les avis latex + + # ------------------------------------------------------------------------------------------------------------------ + def __init__(self, diplome, formation_id): + """ + Création d'une table PE sur la base d'un semestre selectionné. 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: + sem_base: le FormSemestre donnant le semestre à la base du jury PE + semBase: le dictionnaire sem donnant la base du jury (CB: TODO: A supprimer à long term) + meme_programme: si True, impose un même programme pour tous les étudiants participant au jury, + si False, permet des programmes differents + """ + self.promoTagDict = {} + + "L'année du diplome" + self.diplome = diplome + + "La formation associée au diplome" + self.formation_id = formation_id + + "Un zip où ranger les fichiers générés" + self.nom_export_zip = "Jury_PE_%s" % self.diplome + self.zipdata = io.BytesIO() + self.zipfile = ZipFile(self.zipdata, "w") + + + + """Chargement des étudiants à prendre en compte dans le jury""" + pe_comp.pe_print( + f"*** Recherche et chargement des étudiants diplômés en {self.diplome} pour la formation {self.formation_id}" + ) + self.etudiants = EtudiantsJuryPE(self.diplome) # Les infos sur les étudiants + self.etudiants.find_etudiants(self.formation_id) + self.diplomes_ids = self.etudiants.diplomes_ids + + """Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE""" + pe_comp.pe_print("*** Génère les semestres taggués") + self.semestres_taggues = compute_semestres_tag(self.etudiants) + + if pe_comp.PE_DEBUG: + """Intègre le bilan des semestres taggués au zip final""" + for fid in self.semestres_taggues: + formsemestretag = self.semestres_taggues[fid] + filename = formsemestretag.nom.replace(" ", "_") + ".csv" + pe_comp.pe_print(f" - Export csv de {filename} ") + self.add_file_to_zip( + filename, formsemestretag.str_tagtable(), path="details_semestres" + ) + + """Génère les trajectoires (combinaison de semestres suivis + par un étudiant pour atteindre le semestre final d'un aggrégat) + """ + pe_comp.pe_print( + "*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants" + ) + self.trajectoires = TrajectoiresJuryPE(self.diplome) + self.trajectoires.cree_trajectoires(self.etudiants) + + """Génère les moyennes par tags des trajectoires""" + pe_comp.pe_print("*** Calcule les moyennes par tag des trajectoires possibles") + self.trajectoires_tagguees = compute_trajectoires_tag( + self.trajectoires, self.etudiants, self.semestres_taggues + ) + + if pe_comp.PE_DEBUG: + """Intègre le bilan des trajectoires tagguées au zip final""" + for trajectoire_id in self.trajectoires_tagguees: + trajectoire_tagguee = self.trajectoires_tagguees[trajectoire_id] + filename = trajectoire_tagguee.get_repr().replace(" ", "_") + ".csv" + pe_comp.pe_print(f" - Export csv de {filename} ") + self.add_file_to_zip( + filename, + trajectoire_tagguee.str_tagtable(), + path="details_semestres", + ) + + """Génère les interclassements (par promo et) par (nom d') aggrégat""" + pe_comp.pe_print("*** Génère les interclassements par aggrégat") + self.interclassements_taggues = compute_interclassements( + self.etudiants, self.trajectoires, self.trajectoires_tagguees + ) + + if pe_comp.PE_DEBUG: + """Intègre le bilan des aggrégats (par promo) au zip final""" + for nom_aggregat in self.interclassements_taggues: + interclass_tag = self.interclassements_taggues[nom_aggregat] + filename = interclass_tag.get_repr().replace(" ", "_") + ".csv" + pe_comp.pe_print(f" - Export csv de {filename} ") + self.add_file_to_zip( + filename, + interclass_tag.str_tagtable(), + path="details_semestres", + ) + + """Synthèse des éléments du jury PE""" + self.synthese = self.synthetise_juryPE() + + # Export des données => mode 1 seule feuille -> supprimé + pe_comp.pe_print("*** Export du jury de synthese") + filename = "synthese_jury_" + str(self.diplome) + '.xls' + with pd.ExcelWriter(filename, engine="openpyxl") as writer: + for onglet in self.synthese: + df = self.synthese[onglet] + df.to_excel(writer, onglet, index=True, header=True) # écriture dans l'onglet + # worksheet = writer.sheets[onglet] # l'on + + self.zipfile.write(filename) + + """Fin !!!! Tada :)""" + + def add_file_to_zip(self, filename: str, data, path=""): + """Add a file to our zip + All files under NOM_EXPORT_ZIP/ + path may specify a subdirectory + + Args: + filename: Le nom du fichier à intégrer au zip + data: Les données du fichier + path: Un dossier dans l'arborescence du zip + """ + path_in_zip = os.path.join(self.nom_export_zip, path, filename) + self.zipfile.writestr(path_in_zip, data) + + def get_zipped_data(self): + """returns file-like data with a zip of all generated (CSV) files. + Reset file cursor at the beginning ! + """ + if self.zipfile: + self.zipfile.close() + self.zipfile = None + self.zipdata.seek(0) + return self.zipdata + + def do_tags_list(self, interclassements: dict[str, AggregatInterclasseTag]): + """La liste des tags extraites des interclassements""" + tags = [] + for aggregat in interclassements: + interclass = interclassements[aggregat] + if interclass.tags_sorted: + tags.extend(interclass.tags_sorted) + tags = sorted(set(tags)) + return tags + + + # **************************************************************************************************************** # + # Méthodes pour la synthèse du juryPE + # ***************************************************************************************************************** + + def synthetise_juryPE(self): + """Synthétise tous les résultats du jury PE dans des dataframes""" + + pe_comp.pe_print("*** Synthèse finale des moyennes ***") + + synthese = {} + pe_comp.pe_print(" -> Synthèse des données administratives") + synthese["administratif"] = self.df_administratif() + + tags = self.do_tags_list(self.interclassements_taggues) + for tag in tags: + pe_comp.pe_print(f" -> Synthèse du tag {tag}") + synthese[tag] = self.df_tag(tag) + return synthese + + + def df_administratif(self): + """Synthétise toutes les données administratives des étudiants""" + + etudids = list(self.diplomes_ids) + + """Récupération des données des étudiants""" + administratif = {} + nbre_semestres_max = self.etudiants.nbre_etapes_max_diplomes() + + for etudid in etudids: + etudiant = self.etudiants.identites[etudid] + cursus = self.etudiants.cursus[etudid] + formsemestres = cursus["formsemestres"] + + administratif[etudid] = { + "Nom": etudiant.nom, + "Prenom": etudiant.prenom, + "Civilite": etudiant.civilite_str, + "Age": pe_comp.calcul_age(etudiant.date_naissance), + "Date d'entree": cursus["entree"], + "Date de diplome": cursus["diplome"], + "Nbre de semestres": len(formsemestres) + } + + # Ajout des noms de semestres parcourus + etapes = pe_affichage.etapes_du_cursus(formsemestres, nbre_semestres_max) + administratif[etudid] |= etapes + + """Construction du dataframe""" + df = pd.DataFrame.from_dict(administratif, orient='index') + return df + + + def df_tag(self, tag): + """Génère le DataFrame synthétisant les moyennes/classements (groupe, + interclassement promo) pour tous les aggrégats prévus, + tels que fourni dans l'excel final. + + Args: + tag: Un des tags (a minima `but`) + + Returns: + """ + + etudids = list(self.diplomes_ids) + aggregats = pe_comp.TOUS_LES_PARCOURS + + + donnees = {} + + for etudid in etudids: + etudiant = self.etudiants.identites[etudid] + donnees[etudid] = { + "Nom": etudiant.nom, + "Prenom": etudiant.prenom, + "Civilite": etudiant.civilite_str, + } + + for aggregat in aggregats: + """La trajectoire de l'étudiant sur l'aggrégat""" + trajectoire = self.trajectoires.suivi[etudid][aggregat] + """Les moyennes par tag de cette trajectoire""" + if trajectoire: + trajectoire_tagguee = self.trajectoires_tagguees[trajectoire.trajectoire_id] + bilan = trajectoire_tagguee.moyennes_tags[tag] + + donnees[etudid] |= { + f"{aggregat} notes ": f"{bilan['notes'].loc[etudid]:.1f}", + f"{aggregat} class. (groupe)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}", + f"{aggregat} min/moy/max (groupe)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}"} + else: + donnees[etudid] |= { + f"{aggregat} notes ": "-", + f"{aggregat} class. (groupe)": "-", + f"{aggregat} min/moy/max (groupe)": "-" + } + + """L'interclassement""" + interclass = self.interclassements_taggues[aggregat] + if tag in interclass.moyennes_tags: + bilan = interclass.moyennes_tags[tag] + + donnees[etudid] |= { + f"{aggregat} class. (promo)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}", + f"{aggregat} min/moy/max (promo)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}" + } + else: + donnees[etudid] |= { + f"{aggregat} class. (promo)": "-", + f"{aggregat} min/moy/max (promo)": "-" + } + + # Fin de l'aggrégat + + df = pd.DataFrame.from_dict(donnees, orient='index') + return df + + + + def table_syntheseJury(self, mode="singlesheet"): # was str_syntheseJury + """Table(s) du jury + mode: singlesheet ou multiplesheet pour export excel + """ + sT = SeqGenTable() # le fichier excel à générer + + + if mode == "singlesheet": + return sT.get_genTable("singlesheet") + else: + return sT + + +def compute_semestres_tag(etudiants: EtudiantsJuryPE): + """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 + des étudiants (cf. attribut etudiants.cursus). + En crééant le semestre taggué, sont calculées les moyennes/classements par tag associé. + . + + Args: + etudiants: Un groupe d'étudiants participant au jury + + Returns: + Un dictionnaire {fid: SemestreTag(fid)} + """ + + """Création des semestres taggués, de type 'S1', 'S2', ...""" + pe_comp.pe_print("*** Création des semestres taggués") + + formsemestres = etudiants.get_formsemestres( + semestres_recherches=pe_comp.TOUS_LES_SEMESTRES + ) + + semestres_tags = {} + for frmsem_id, formsemestre in formsemestres.items(): + """Choix d'un nom pour le semestretag""" + nom = "S%d %d %d-%d" % ( + formsemestre.semestre_id, + frmsem_id, + formsemestre.date_debut.year, + formsemestre.date_fin.year, + ) + + pe_comp.pe_print(f" --> Semestre taggué {nom} sur la base de {formsemestre}") + + """Créé le semestre_tag et exécute les calculs de moyennes""" + formsemestretag = SemestreTag(nom, frmsem_id) + + """Stocke le semestre taggué""" + semestres_tags[frmsem_id] = formsemestretag + + return semestres_tags + + +def compute_trajectoires_tag( + trajectoires: TrajectoiresJuryPE, + etudiants: EtudiantsJuryPE, + semestres_taggues: dict[int, SemestreTag], +): + """Créée les trajectoires tagguées (combinaison aggrégeant plusieurs semestres au sens + d'un aggrégat (par ex: '3S')), + en calculant les moyennes et les classements par tag pour chacune. + + Pour rappel : Chaque trajectoire est identifiée un nom d'aggrégat et par un formsemestre terminal. + + Par exemple : + + * combinaisons '3S' : S1+S2+S3 en prenant en compte tous les S3 qu'ont fréquenté les + étudiants du jury PE. Ces S3 marquent les formsemestre terminal de chaque combinaison. + + * combinaisons 'S2' : 1 seul S2 pour des étudiants n'ayant pas redoublé, 2 pour des redoublants (dont les + notes seront moyennées sur leur 2 semestres S2). Ces combinaisons ont pour formsemestre le dernier S2 en + date (le S2 redoublé par les redoublants est forcément antérieur) + + + Args: + etudiants: Les données des étudiants + semestres_tag: Les semestres tag (pour lesquels des moyennes par tag ont été calculés) + + Return: + Un dictionnaire de la forme {nom_aggregat: {fid_terminal: SetTag(fid_terminal)} } + """ + + pe_comp.pe_print(" *** Création des aggrégats ") + + trajectoires_tagguees = {} + + for trajectoire_id in trajectoires.trajectoires: + trajectoire = trajectoires.trajectoires[trajectoire_id] + nom = trajectoire.get_repr() + + pe_comp.pe_print(f" --> Fusion {nom}") + + """Création de la trajectoire_tagguee associée""" + trajectoire_tagguee = TrajectoireTag( + nom, trajectoire, semestres_taggues, etudiants + ) + + """Mémorise le résultat""" + trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee + + return trajectoires_tagguees + + +def compute_interclassements( + etudiants: EtudiantsJuryPE, + trajectoires_jury_pe: TrajectoiresJuryPE, + trajectoires_tagguees: dict[tuple, Trajectoire], +): + """Interclasse les étudiants, (nom d') aggrégat par aggrégat, + pour fournir un classement sur la promo. Le classement est établi au regard du nombre + d'étudiants ayant participé au même aggrégat. + """ + pe_comp.pe_print(" Interclassement sur la promo") + + aggregats_interclasses_taggues = {} + for nom_aggregat in pe_comp.TOUS_LES_SEMESTRES + pe_comp.TOUS_LES_AGGREGATS: + pe_comp.pe_print(f" --> {nom_aggregat}") + interclass = AggregatInterclasseTag( + nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees + ) + aggregats_interclasses_taggues[nom_aggregat] = interclass + return aggregats_interclasses_taggues diff --git a/app/pe/pe_jurype.py b/app/pe/pe_jurype.py deleted file mode 100644 index 932d03e4..00000000 --- a/app/pe/pe_jurype.py +++ /dev/null @@ -1,736 +0,0 @@ -# -*- mode: python -*- -# -*- coding: utf-8 -*- - -############################################################################## -# -# 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 Fri Sep 9 09:15:05 2016 - -@author: barasc -""" -import datetime - -# ---------------------------------------------------------- -# Ensemble des fonctions et des classes -# permettant les calculs preliminaires (hors affichage) -# a l'edition d'un jury de poursuites d'etudes -# ---------------------------------------------------------- - -import io -import os -from zipfile import ZipFile - - -from app.comp import res_sem -from app.comp.res_compat import NotesTableCompat -from app.comp.res_sem import load_formsemestre_results -from app.models import Formation, FormSemestre -from app.models.etudiants import Identite -from app.pe.pe_semestretag import SemestreTag -from app.pe.pe_interclasstag import AggregatInterclasseTag -from app.pe.pe_trajectoiretag import TrajectoireTag - -from app.scodoc.gen_tables import GenTable, SeqGenTable -import app.scodoc.sco_utils as scu -from app.scodoc import codes_cursus -from app.pe import pe_tools -from app.pe import pe_semestretag -from app.pe.pe_etudiant import EtudiantsJuryPE -from app.pe.pe_trajectoire import TrajectoiresJuryPE, Trajectoire - - -# ---------------------------------------------------------------------------------------- -def comp_nom_semestre_dans_parcours(sem): - """Le nom a afficher pour titrer un semestre - par exemple: "semestre 2 FI 2015" - """ - formation: Formation = Formation.query.get_or_404(sem["formation_id"]) - parcours = codes_cursus.get_cursus_from_code(formation.type_parcours) - return "%s %s %s %s" % ( - parcours.SESSION_NAME, # eg "semestre" - sem["semestre_id"], # eg 2 - sem.get("modalite", ""), # eg FI ou FC - sem["annee_debut"], # eg 2015 - ) - - -# ---------------------------------------------------------------------------------------- -class JuryPE(object): - """Classe mémorisant toutes les informations nécessaires pour établir un jury de PE. - Modèle basé sur NotesTable. - - Attributs : - - * diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX) - * juryEtudDict : dictionnaire récapitulant les étudiants participant au jury PE (données administratives + - celles des semestres valides à prendre en compte permettant le calcul des moyennes ... - ``{'etudid : { 'nom', 'prenom', 'civilite', 'diplome', '', }}`` - - Rq: il contient à la fois les étudiants qui vont être diplomés à la date prévue - et ceux qui sont éliminés (abandon, redoublement, ...) pour affichage alternatif - """ - - # Variables de classe décrivant les aggrégats, leur ordre d'apparition temporelle et - # leur affichage dans les avis latex - - # ------------------------------------------------------------------------------------------------------------------ - def __init__(self, diplome, formation_id): - """ - Création d'une table PE sur la base d'un semestre selectionné. 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: - sem_base: le FormSemestre donnant le semestre à la base du jury PE - semBase: le dictionnaire sem donnant la base du jury (CB: TODO: A supprimer à long term) - meme_programme: si True, impose un même programme pour tous les étudiants participant au jury, - si False, permet des programmes differents - """ - self.promoTagDict = {} - - "L'année du diplome" - self.diplome = diplome - - "La formation associée au diplome" - self.formation_id = formation_id - - "Un zip où ranger les fichiers générés" - self.nom_export_zip = "Jury_PE_%s" % self.diplome - self.zipdata = io.BytesIO() - self.zipfile = ZipFile(self.zipdata, "w") - - self.syntheseJury = {} # Le jury de synthèse - - """Chargement des étudiants à prendre en compte dans le jury""" - pe_tools.pe_print( - f"*** Recherche et chargement des étudiants diplômés en {self.diplome} pour la formation {self.formation_id}" - ) - self.etudiants = EtudiantsJuryPE(self.diplome) # Les infos sur les étudiants - self.etudiants.find_etudiants(self.formation_id) - - """Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE""" - pe_tools.pe_print("*** Génère les semestres taggués") - self.semestres_taggues = compute_semestres_tag(self.etudiants) - - if pe_tools.PE_DEBUG: - """Intègre le bilan des semestres taggués au zip final""" - for fid in self.semestres_taggues: - formsemestretag = self.semestres_taggues[fid] - filename = formsemestretag.nom.replace(" ", "_") + ".csv" - pe_tools.pe_print(f" - Export csv de {filename} ") - self.add_file_to_zip( - filename, formsemestretag.str_tagtable(), path="details_semestres" - ) - - """Génère les trajectoires (combinaison de semestres suivis - par un étudiant pour atteindre le semestre final d'un aggrégat) - """ - pe_tools.pe_print( - "*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants" - ) - self.trajectoires = TrajectoiresJuryPE(self.diplome) - self.trajectoires.cree_trajectoires(self.etudiants) - - """Génère les moyennes par tags des trajectoires""" - pe_tools.pe_print("*** Calcule les moyennes par tag des trajectoires possibles") - self.trajectoires_tagguees = compute_trajectoires_tag( - self.trajectoires, self.etudiants, self.semestres_taggues - ) - - if pe_tools.PE_DEBUG: - """Intègre le bilan des trajectoires tagguées au zip final""" - for trajectoire_id in self.trajectoires_tagguees: - trajectoire_tagguee = self.trajectoires_tagguees[trajectoire_id] - filename = trajectoire_tagguee.get_repr().replace(" ", "_") + ".csv" - pe_tools.pe_print(f" - Export csv de {filename} ") - self.add_file_to_zip( - filename, - trajectoire_tagguee.str_tagtable(), - path="details_semestres", - ) - - """Génère les interclassements (par promo et) par (nom d') aggrégat""" - pe_tools.pe_print("*** Génère les interclassements par aggrégat") - self.interclassements_taggues = compute_interclassements( - self.etudiants, self.trajectoires, self.trajectoires_tagguees - ) - - if pe_tools.PE_DEBUG: - """Intègre le bilan des aggrégats (par promo) au zip final""" - for nom_aggregat in self.interclassements_taggues: - interclass_tag = self.interclassements_taggues[nom_aggregat] - filename = interclass_tag.get_repr().replace(" ", "_") + ".csv" - pe_tools.pe_print(f" - Export csv de {filename} ") - self.add_file_to_zip( - filename, - interclass_tag.str_tagtable(), - path="details_semestres", - ) - - """Synthèse des éléments du jury PE""" - self.synthetise_juryPE() - - # Export des données => mode 1 seule feuille -> supprimé - # filename = self.NOM_EXPORT_ZIP + "jurySyntheseDict_" + str(self.diplome) + '.xls' - # self.xls = self.table_syntheseJury(mode="singlesheet") - # self.add_file_to_zip(filename, self.xls.excel()) - - # Fabrique 1 fichier excel résultat avec 1 seule feuille => trop gros - if False: - filename = self.nom_export_zip + "_jurySyntheseDict" + scu.XLSX_SUFFIX - self.xlsV2 = self.table_syntheseJury(mode="multiplesheet") - if self.xlsV2: - pe_tools.add_file_to_zip( - self.nom_export_zip, filename, self.xlsV2.excel() - ) - - # Pour debug - # self.syntheseJury = pe_tools.JURY_SYNTHESE_POUR_DEBUG #Un dictionnaire fictif pour debug - - def add_file_to_zip(self, filename: str, data, path=""): - """Add a file to our zip - All files under NOM_EXPORT_ZIP/ - path may specify a subdirectory - - Args: - filename: Le nom du fichier à intégrer au zip - data: Les données du fichier - path: Un dossier dans l'arborescence du zip - """ - path_in_zip = os.path.join(self.nom_export_zip, path, filename) - self.zipfile.writestr(path_in_zip, data) - - def get_zipped_data(self): - """returns file-like data with a zip of all generated (CSV) files. - Reset file cursor at the beginning ! - """ - if self.zipfile: - self.zipfile.close() - self.zipfile = None - self.zipdata.seek(0) - return self.zipdata - - # **************************************************************************************************************** # - # Traitements des semestres impliqués dans le jury - # **************************************************************************************************************** # - - # **************************************************************************************************************** # - # Méthodes pour la synthèse du juryPE - # ***************************************************************************************************************** - def synthetise_juryPE(self): - """Synthétise tous les résultats du jury PE dans des dataframess""" - self.syntheseJury = {} - for etudid in self.etudiants.get_etudids(self.diplome): - etudinfo = self.ETUDINFO_DICT[etudid] - self.syntheseJury[etudid] = { - "nom": etudinfo["nom"], - "prenom": etudinfo["prenom"], - "civilite": etudinfo["civilite"], - "civilite_str": etudinfo["civilite_str"], - "age": str(pe_tools.calcul_age(etudinfo["date_naissance"])), - "lycee": etudinfo["nomlycee"] - + ( - " (" + etudinfo["villelycee"] + ")" - if etudinfo["villelycee"] != "" - else "" - ), - "bac": etudinfo["bac"], - "code_nip": etudinfo["code_nip"], # pour la photo - "entree": self.get_dateEntree(etudid), - "promo": self.diplome, - } - # Le parcours - self.syntheseJury[etudid]["parcours"] = self.get_parcoursIUT( - etudid - ) # liste des semestres - self.syntheseJury[etudid]["nbSemestres"] = len( - self.syntheseJury[etudid]["parcours"] - ) # nombre de semestres - - # Ses résultats - for nom in pe_tools.PARCOURS: # S1, puis S2, puis 1A - # dans le groupe : la table tagguée dans les semtag ou les settag si aggrégat - self.syntheseJury[etudid][nom] = {"groupe": {}, "promo": {}} - if ( - self.etudiants.cursus[etudid][nom] != None - ): # Un parcours valide existe - if nom in pe_tools.TOUS_LES_SEMESTRES: - tagtable = self.semestres_taggues[ - self.etudiants.cursus[etudid][nom] - ] - else: - tagtable = self.trajectoires_tagguees[nom][ - self.etudiants.cursus[etudid][nom] - ] - for tag in tagtable.get_all_tags(): - self.syntheseJury[etudid][nom]["groupe"][ - tag - ] = tagtable.get_resultatsEtud( - tag, etudid - ) # Le tuple des résultats - - # interclassé dans la promo - tagtable = self.promoTagDict[nom] - for tag in tagtable.get_all_tags(): - self.syntheseJury[etudid][nom]["promo"][ - tag - ] = tagtable.get_resultatsEtud(tag, etudid) - - def get_dateEntree(self, etudid): - """Renvoie l'année d'entrée de l'étudiant à l'IUT""" - # etudinfo = self.ETUDINFO_DICT[etudid] - semestres = self.get_semestresBUT_d_un_etudiant(etudid) - if semestres: - # le 1er sem à l'IUT - return semestres[0]["annee_debut"] - else: - return "" - - def get_parcoursIUT(self, etudid): - """Renvoie une liste d'infos sur les semestres du parcours d'un étudiant""" - # etudinfo = self.ETUDINFO_DICT[etudid] - sems = self.etudiants.semestres_etudiant(etudid) - - infos = [] - for sem in sems: - nomsem = comp_nom_semestre_dans_parcours(sem) - infos.append( - { - "nom_semestre_dans_parcours": nomsem, - "titreannee": sem["titreannee"], - "formsemestre_id": sem["formsemestre_id"], # utile dans le futur ? - } - ) - return infos - - # **************************************************************************************************************** # - # Méthodes d'affichage pour debug - # **************************************************************************************************************** # - def str_etudiants_in_jury(self, delim=";"): - # En tete: - entete = ["Id", "Nom", "Abandon", "Diplome"] - for nom_sem in pe_tools.TOUS_LES_PARCOURS: - entete += [nom_sem, "descr"] - chaine = delim.join(entete) + "\n" - - for etudid in self.etudiants.cursus: - donnees = self.etudiants.cursus[etudid] - # pe_tools.pe_print(etudid, donnees) - # les infos générales - descr = [ - etudid, - donnees["nom"], - str(donnees["abandon"]), - str(donnees["diplome"]), - ] - - # les semestres et les aggrégats - for nom_sem in pe_tools.TOUS_LES_PARCOURS: - table = ( - self.semestres_taggues[donnees[nom_sem]].nom - if donnees[nom_sem] in self.semestres_taggues - else "manquant" - ) - descr += [ - donnees[nom_sem] if donnees[nom_sem] != None else "manquant", - table, - ] - - chaine += delim.join(descr) + "\n" - return chaine - - # - def export_juryPEDict(self): - """Export csv de self.PARCOURSINFO_DICT""" - fichier = "juryParcoursDict_" + str(self.diplome) - pe_tools.pe_print(" -> Export de " + fichier) - filename = self.nom_export_zip + fichier + ".csv" - self.zipfile.writestr(filename, self.str_etudiants_in_jury()) - - def get_allTagForAggregat(self, nom_aggregat): - """Extrait du dictionnaire syntheseJury la liste des tags d'un semestre ou - d'un aggrégat donné par son nom (S1, S2, S3 ou S4, 1A, ...). Renvoie [] si aucun tag. - """ - taglist = set() - for etudid in self.etudiants.get_etudids(): - taglist = taglist.union( - set(self.syntheseJury[etudid][nom_aggregat]["groupe"].keys()) - ) - taglist = taglist.union( - set(self.syntheseJury[etudid][nom_aggregat]["promo"].keys()) - ) - return list(taglist) - - def get_allTagInSyntheseJury(self): - """Extrait tous les tags du dictionnaire syntheseJury trié par - ordre alphabétique. [] si aucun tag""" - allTags = set() - for nom in pe_tools.TOUS_LES_PARCOURS: - allTags = allTags.union(set(self.get_allTagForAggregat(nom))) - return sorted(list(allTags)) if len(allTags) > 0 else [] - - def table_syntheseJury(self, mode="singlesheet"): # was str_syntheseJury - """Table(s) du jury - mode: singlesheet ou multiplesheet pour export excel - """ - sT = SeqGenTable() # le fichier excel à générer - - # Les etudids des étudiants à afficher, triés par ordre alphabétiques de nom+prénom - donnees_tries = sorted( - [ - ( - etudid, - self.syntheseJury[etudid]["nom"] - + " " - + self.syntheseJury[etudid]["prenom"], - ) - for etudid in self.syntheseJury.keys() - ], - key=lambda c: c[1], - ) - etudids = [e[0] for e in donnees_tries] - if not etudids: # Si pas d'étudiants - T = GenTable( - columns_ids=["pas d'étudiants"], - rows=[], - titles={"pas d'étudiants": "pas d'étudiants"}, - html_sortable=True, - xls_sheet_name="but", - ) - sT.add_genTable("but", T) - return sT - - # Si des étudiants - maxParcours = max( - [self.syntheseJury[etudid]["nbSemestres"] for etudid in etudids] - ) - - infos = ["civilite", "nom", "prenom", "age", "nbSemestres"] - entete = ["etudid"] - entete.extend(infos) - entete.extend(["P%d" % i for i in range(1, maxParcours + 1)]) - champs = [ - "note", - "class groupe", - "class promo", - "min/moy/max groupe", - "min/moy/max promo", - ] - - # Les aggrégats à afficher par ordre tel que indiqué dans le dictionnaire parcours - aggregats = list(pe_tools.PARCOURS.keys()) # ['S1', 'S2', ..., '1A', '4S'] - # aggregats = sorted( - # aggregats, key=lambda t: pe_tools.PARCOURS[t]["ordre"] - # ) # Tri des aggrégats - - if mode == "multiplesheet": - allSheets = ( - self.get_allTagInSyntheseJury() - ) # tous les tags de syntheseJuryDict - allSheets = sorted(allSheets) # Tri des tags par ordre alphabétique - for sem in pe_tools.TOUS_LES_PARCOURS: - entete.extend(["%s %s" % (sem, champ) for champ in champs]) - else: # "singlesheet" - allSheets = ["singlesheet"] - for ( - sem - ) in ( - pe_tools.TOUS_LES_PARCOURS - ): # pe_tools.PARCOURS.keys() -> ['S1', 'S2', ..., '1A', '4S'] - tags = self.get_allTagForAggregat(sem) - entete.extend( - ["%s %s %s" % (sem, tag, champ) for tag in tags for champ in champs] - ) - - columns_ids = entete # les id et les titres de colonnes sont ici identiques - titles = {i: i for i in columns_ids} - - for ( - sheet - ) in ( - allSheets - ): # Pour tous les sheets à générer (1 si singlesheet, autant que de tags si multiplesheet) - rows = [] - for etudid in etudids: - e = self.syntheseJury[etudid] - # Les info générales: - row = { - "etudid": etudid, - "civilite": e["civilite"], - "nom": e["nom"], - "prenom": e["prenom"], - "age": e["age"], - "nbSemestres": e["nbSemestres"], - } - # Les parcours: P1, P2, ... - n = 1 - for p in e["parcours"]: - row["P%d" % n] = p["titreannee"] - n += 1 - # if self.syntheseJury[etudid]['nbSemestres'] < maxParcours: - # descr += delim.join( ['']*( maxParcours -self.syntheseJury[etudid]['nbSemestres']) ) + delim - for sem in aggregats: # pe_tools.PARCOURS.keys(): - listeTags = ( - self.get_allTagForAggregat(sem) - if mode == "singlesheet" - else [sheet] - ) - for tag in listeTags: - if tag in self.syntheseJury[etudid][sem]["groupe"]: - resgroupe = self.syntheseJury[etudid][sem]["groupe"][ - tag - ] # tuple - else: - resgroupe = (None, None, None, None, None, None, None) - if tag in self.syntheseJury[etudid][sem]["promo"]: - respromo = self.syntheseJury[etudid][sem]["promo"][tag] - else: - respromo = (None, None, None, None, None, None, None) - - # note = "%2.2f" % resgroupe[0] if isinstance(resgroupe[0], float) else str(resgroupe[0]) - champ = ( - "%s %s " % (sem, tag) - if mode == "singlesheet" - else "%s " % (sem) - ) - row[champ + "note"] = scu.fmt_note(resgroupe[0]) - row[champ + "class groupe"] = "%s / %s" % ( - resgroupe[2] if resgroupe[2] else "-", - resgroupe[3] if resgroupe[3] else "-", - ) - row[champ + "class promo"] = "%s / %s" % ( - respromo[2] if respromo[2] else "-", - respromo[3] if respromo[3] else "-", - ) - row[champ + "min/moy/max groupe"] = "%s / %s / %s" % tuple( - (scu.fmt_note(x) if x is not None else "-") - for x in (resgroupe[6], resgroupe[4], resgroupe[5]) - ) - row[champ + "min/moy/max promo"] = "%s / %s / %s" % tuple( - (scu.fmt_note(x) if x is not None else "-") - for x in (respromo[6], respromo[4], respromo[5]) - ) - rows.append(row) - - T = GenTable( - columns_ids=columns_ids, - rows=rows, - titles=titles, - html_sortable=True, - xls_sheet_name=sheet, - ) - sT.add_genTable(sheet, T) - - if mode == "singlesheet": - return sT.get_genTable("singlesheet") - else: - return sT - - # **************************************************************************************************************** # - # Méthodes de classe pour gestion d'un cache de données accélérant les calculs / intérêt à débattre - # **************************************************************************************************************** # - - # ------------------------------------------------------------------------------------------------------------------ - def get_cache_etudInfo_d_un_etudiant(self, etudid): - """Renvoie les informations sur le parcours d'un étudiant soit en les relisant depuis - ETUDINFO_DICT si mémorisée soit en les chargeant et en les mémorisant - - TODO:: A supprimer à long terme - """ - if etudid not in self.ETUDINFO_DICT: - self.ETUDINFO_DICT[etudid] = Identite.get_etud(etudid=etudid) - # sco_etud.get_etud_info( - # etudid=etudid, filled=True - # ))[0] - return self.ETUDINFO_DICT[etudid] - - # ------------------------------------------------------------------------------------------------------------------ - - # ------------------------------------------------------------------------------------------------------------------ - def get_cache_notes_d_un_semestre(self, formsemestre_id: int) -> NotesTableCompat: - """Charge la table des notes d'un formsemestre""" - formsemestre = FormSemestre.get_formsemestre(formsemestre_id) - return res_sem.load_formsemestre_results(formsemestre) - - # ------------------------------------------------------------------------------------------------------------------ - - # ------------------------------------------------------------------------------------------------------------------ - def get_semestresBUT_d_un_etudiant(self, identite: Identite, semestre_id=None): - """cf. pe_etudiant.semestres_etudiant()""" - - return None - - # ********************************************* - # Fonctions d'affichage pour debug - def get_resultat_d_un_etudiant(self, etudid): - chaine = "" - for nom_sem in pe_tools.TOUS_LES_SEMESTRES: - semtagid = self.etudiants.cursus[etudid][ - nom_sem - ] # le formsemestre_id du semestre taggué de l'étudiant - semtag = self.semestres_taggues[semtagid] - chaine += "Semestre " + nom_sem + str(semtagid) + "\n" - # le détail du calcul tag par tag - # chaine += "Détail du calcul du tag\n" - # chaine += "-----------------------\n" - # for tag in semtag.taglist: - # chaine += "Tag=" + tag + "\n" - # chaine += semtag.str_detail_resultat_d_un_tag(tag, etudid=etudid) + "\n" - # le bilan des tags - chaine += "Bilan des tags\n" - chaine += "--------------\n" - for tag in semtag.taglist: - chaine += ( - tag + ";" + semtag.str_resTag_d_un_etudiant(tag, etudid) + "\n" - ) - chaine += "\n" - return chaine - - def get_date_entree_etudiant(self, etudid) -> str: - """Renvoie la date d'entree d'un étudiant: "1996" """ - annees_debut = [ - int(sem["annee_debut"]) for sem in self.ETUDINFO_DICT[etudid]["sems"] - ] - if annees_debut: - return str(min(annees_debut)) - return "" - - -def compute_semestres_tag(etudiants: EtudiantsJuryPE): - """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 - des étudiants (cf. attribut etudiants.cursus). - En crééant le semestre taggué, sont calculées les moyennes/classements par tag associé. - . - - Args: - etudiants: Un groupe d'étudiants participant au jury - - Returns: - Un dictionnaire {fid: SemestreTag(fid)} - """ - - """Création des semestres taggués, de type 'S1', 'S2', ...""" - pe_tools.pe_print("*** Création des semestres taggués") - - formsemestres = etudiants.get_formsemestres( - semestres_recherches=pe_tools.TOUS_LES_SEMESTRES - ) - - semestres_tags = {} - for frmsem_id, formsemestre in formsemestres.items(): - """Choix d'un nom pour le semestretag""" - nom = "S%d %d %d-%d" % ( - formsemestre.semestre_id, - frmsem_id, - formsemestre.date_debut.year, - formsemestre.date_fin.year, - ) - - pe_tools.pe_print(f" --> Semestre taggué {nom} sur la base de {formsemestre}") - - """Créé le semestre_tag et exécute les calculs de moyennes""" - formsemestretag = pe_semestretag.SemestreTag(nom, frmsem_id) - - """Stocke le semestre taggué""" - semestres_tags[frmsem_id] = formsemestretag - - return semestres_tags - - -def compute_trajectoires_tag( - trajectoires: TrajectoiresJuryPE, - etudiants: EtudiantsJuryPE, - semestres_taggues: dict[int, SemestreTag], -): - """Créée les trajectoires tagguées (combinaison aggrégeant plusieurs semestres au sens - d'un aggrégat (par ex: '3S')), - en calculant les moyennes et les classements par tag pour chacune. - - Pour rappel : Chaque trajectoire est identifiée un nom d'aggrégat et par un formsemestre terminal. - - Par exemple : - - * combinaisons '3S' : S1+S2+S3 en prenant en compte tous les S3 qu'ont fréquenté les - étudiants du jury PE. Ces S3 marquent les formsemestre terminal de chaque combinaison. - - * combinaisons 'S2' : 1 seul S2 pour des étudiants n'ayant pas redoublé, 2 pour des redoublants (dont les - notes seront moyennées sur leur 2 semestres S2). Ces combinaisons ont pour formsemestre le dernier S2 en - date (le S2 redoublé par les redoublants est forcément antérieur) - - - Args: - etudiants: Les données des étudiants - semestres_tag: Les semestres tag (pour lesquels des moyennes par tag ont été calculés) - - Return: - Un dictionnaire de la forme {nom_aggregat: {fid_terminal: SetTag(fid_terminal)} } - """ - - pe_tools.pe_print(" *** Création des aggrégats ") - - trajectoires_tagguees = {} - - for trajectoire_id in trajectoires.trajectoires: - trajectoire = trajectoires.trajectoires[trajectoire_id] - nom = trajectoire.get_repr() - - pe_tools.pe_print(f" --> Fusion {nom}") - - """Création de la trajectoire_tagguee associée""" - trajectoire_tagguee = TrajectoireTag( - nom, trajectoire, semestres_taggues, etudiants - ) - - """Mémorise le résultat""" - trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee - - return trajectoires_tagguees - - -def compute_interclassements( - etudiants: EtudiantsJuryPE, - trajectoires_jury_pe: TrajectoiresJuryPE, - trajectoires_tagguees: dict[tuple, Trajectoire], -): - """Interclasse les étudiants, (nom d') aggrégat par aggrégat, - pour fournir un classement sur la promo. Le classement est établi au regard du nombre - d'étudiants ayant participé au même aggrégat. - """ - pe_tools.pe_print(" Interclassement sur la promo") - - aggregats_interclasses_taggues = {} - for nom_aggregat in pe_tools.TOUS_LES_SEMESTRES + pe_tools.TOUS_LES_AGGREGATS: - pe_tools.pe_print(f" --> {nom_aggregat}") - interclass = AggregatInterclasseTag( - nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees - ) - aggregats_interclasses_taggues[nom_aggregat] = interclass - return aggregats_interclasses_taggues diff --git a/app/pe/pe_semestretag.py b/app/pe/pe_semtag.py similarity index 97% rename from app/pe/pe_semestretag.py rename to app/pe/pe_semtag.py index a97c5000..dbf0c02a 100644 --- a/app/pe/pe_semestretag.py +++ b/app/pe/pe_semtag.py @@ -37,22 +37,18 @@ Created on Fri Sep 9 09:15:05 2016 """ from app import db, log -from app.comp import res_sem, inscr_mod, moy_ue, moy_sem -from app.comp.res_common import ResultatsSemestre +from app.comp import res_sem, moy_ue, moy_sem from app.comp.res_compat import NotesTableCompat from app.comp.res_sem import load_formsemestre_results -from app.models import FormSemestre, Identite, DispenseUE +from app.models import FormSemestre from app.models.moduleimpls import ModuleImpl -from app.pe import pe_tagtable -from app.pe import pe_tools -from app.scodoc import codes_cursus, sco_preferences from app.scodoc import sco_tag_module -from app.scodoc import sco_utils as scu from app.scodoc.codes_cursus import UE_SPORT +import app.pe.pe_comp as pe_comp +from app.pe.pe_tabletags import (TableTag, TAGS_RESERVES) - -class SemestreTag(pe_tagtable.TableTag): +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. @@ -67,10 +63,8 @@ class SemestreTag(pe_tagtable.TableTag): nom: Nom à donner au SemestreTag formsemestre_id: Identifiant du FormSemestre sur lequel il se base """ - pe_tagtable.TableTag.__init__( - self, - nom=nom - ) + TableTag.__init__(self, nom=nom) + """Le semestre""" self.formsemestre_id = formsemestre_id self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id) @@ -97,7 +91,7 @@ class SemestreTag(pe_tagtable.TableTag): """Les tags (en supprimant les tags réservés)""" self.tags = get_synthese_tags_semestre(self.nt.formsemestre) - for tag in pe_tagtable.TAGS_RESERVES: + for tag in TAGS_RESERVES: if tag in self.tags: del self.tags[tag] @@ -105,7 +99,7 @@ class SemestreTag(pe_tagtable.TableTag): self.moyennes_tags = {} for tag in self.tags: - pe_tools.pe_print(f" -> Traitement du tag {tag}") + pe_comp.pe_print(f" -> Traitement du tag {tag}") moy_gen_tag = self.compute_moyenne_tag(tag) class_gen_tag = moy_sem.comp_ranks_series(moy_gen_tag)[1] # en int self.moyennes_tags[tag] = { @@ -118,7 +112,7 @@ class SemestreTag(pe_tagtable.TableTag): } """Ajoute les moyennes générales de BUT pour le semestre considéré""" - pe_tools.pe_print(f" -> Traitement du tag but") + pe_comp.pe_print(f" -> Traitement du tag but") moy_gen_but = self.nt.etud_moy_gen class_gen_but = self.nt.etud_moy_gen_ranks_int self.moyennes_tags["but"] = { diff --git a/app/pe/pe_settag_interclasse.py b/app/pe/pe_settag_interclasse.py deleted file mode 100644 index 83abe82d..00000000 --- a/app/pe/pe_settag_interclasse.py +++ /dev/null @@ -1,128 +0,0 @@ -from app.pe import pe_tagtable -from app.pe.pe_tools import PE_DEBUG, pe_print -import app.pe.pe_etudiant as pe_etudiant -from app.pe.pe_etudiant import EtudiantsJuryPE - -class SetTagInterClasse(pe_tagtable.TableTag): - """Interclasse les étudiants d'une promo (ceux diplômé) par aggrégat de même nom - (par ex: un "3S"), pour stocker leur moyenne et fournir un classement de "promo". - - Les - """ - - # ------------------------------------------------------------------------------------------------------------------- - def __init__(self, nom:str, etudiants: EtudiantsJuryPE, aggregats_taggues: dict[str, dict]): - """""" - pe_tagtable.TableTag.__init__(self, nom) - self.etudiants = etudiants - self.aggregats_taggues = aggregats_taggues # Les moyennes par aggrégats - - - # ------------------------------------------------------------------------------------------- - def set_Etudiants(self, etudiants, juryPEDict, etudInfoDict, nom_sem_final=None): - """Détermine la liste des étudiants à prendre en compte, en partant de - la liste fournie en paramètre et en vérifiant que l'étudiant dispose bien d'un parcours valide pour la combinaison demandée. - Renvoie le nombre d'étudiants effectivement inscrits.""" - if nom_sem_final: - self.nom += "_" + nom_sem_final - for etudid in etudiants: - if juryPEDict[etudid][self.combinaison] != None: - self.inscrlist.append(etudInfoDict[etudid]) - self.identdict[etudid] = etudInfoDict[etudid] - self.parcoursDict[etudid] = juryPEDict[etudid] - return len(self.inscrlist) - - # ------------------------------------------------------------------------------------------- - def get_Fids_in_settag(self): - """Renvoie la liste des semestres (les formsemestre_id finissant la combinaison par ex. '3S' dont les fid des S3) à prendre en compte - pour les moyennes, en considérant tous les étudiants inscrits""" - return list( - {self.parcoursDict[etudid][self.combinaison] for etudid in self.identdict} - ) - - # --------------------------------------------------------------------------------------------- - def set_SetTagDict(self, SetTagDict): - """Mémorise les settag nécessaires au jury.""" - self.SetTagDict = { - fid: SetTagDict[fid] for fid in self.get_Fids_in_settag() if fid != None - } - if PE_DEBUG >= 1: - pe_print(" => %d semestres utilisés" % len(self.SetTagDict)) - - # ------------------------------------------------------------------------------------------------------------------- - def comp_data_settag(self): - """Calcule tous les données numériques relatives au settag""" - # Attributs relatifs aux tag pour les modules pris en compte - self.taglist = self.do_taglist() - - # if PE_DEBUG >= 1: pe_print(" => Tags = " + ", ".join( self.taglist )) - - # Calcul des moyennes de chaque étudiant par tag - reussiteAjoutTag = {"OK": [], "KO": []} - for tag in self.taglist: - moyennes = self.get_MoyennesSetTag(tag, force=False) - res = self.add_moyennesTag(tag, moyennes) # pas de notes => pas de moyenne - reussiteAjoutTag["OK" if res else "KO"].append(tag) - if len(reussiteAjoutTag["OK"]) > 0 and PE_DEBUG: - pe_print( - " => Interclassement de %d tags : " % (len(reussiteAjoutTag["OK"])) - + ", ".join(reussiteAjoutTag["OK"]) - ) - if len(reussiteAjoutTag["KO"]) > 0 and PE_DEBUG: - pe_print( - " => %d tags manquants : " % (len(reussiteAjoutTag["KO"])) - + ", ".join(reussiteAjoutTag["KO"]) - ) - - # ------------------------------------------------------------------------------------------------------------------- - def get_etudids(self): - return list(self.identdict.keys()) - - # ------------------------------------------------------------------------------------------------------------------- - def do_taglist(self): - """Parcourt les tags des semestres taggués et les synthétise sous la forme - d'une liste en supprimant les doublons - """ - ensemble = [] - for settag in self.SetTagDict.values(): - ensemble.extend(settag.get_all_tags()) - return sorted(list(set(ensemble))) - - # ------------------------------------------------------------------------------------------------------------------- - def get_NotesEtCoeffsSetTagEtudiant(self, tag, etudid): - """Récupère tous les notes et les coeffs d'un étudiant relatives à un tag dans ses semestres valides et les renvoie dans un tuple (notes, coeffs) - avec notes et coeffs deux listes""" - leSetTagDeLetudiant = self.parcoursDict[etudid][self.combinaison] - - note = self.SetTagDict[leSetTagDeLetudiant].get_moy_from_resultats(tag, etudid) - coeff = self.SetTagDict[leSetTagDeLetudiant].get_coeff_from_resultats( - tag, etudid - ) - return (note, coeff) - - # ------------------------------------------------------------------------------------------------------------------- - def get_MoyennesSetTag(self, tag, force=False): - """Renvoie les "moyennes" des étudiants à un tag donné, en prenant en compte tous les settag de l'aggrégat, - et leur coeff Par moyenne, s'entend une note moyenne, la somme des coefficients de pondération - appliqué dans cette moyenne. - - Force ou non le calcul de la moyenne lorsque des notes sont manquantes. - - Renvoie les informations sous la forme d'une liste [etudid: (moy, somme_coeff_normalisée, rang), ...} - """ - # if tag not in self.get_all_tags() : return None - - # Calcule les moyennes - lesMoyennes = [] - for ( - etudid - ) in ( - self.get_etudids() - ): # Pour tous les étudiants non défaillants du semestre inscrits dans des modules relatifs au tag - (moyenne, somme_coeffs) = self.get_NotesEtCoeffsSetTagEtudiant( - tag, etudid - ) # lecture des notes associées au tag - lesMoyennes += [ - (moyenne, somme_coeffs, etudid) - ] # Un tuple (pour classement résumant les données) - return lesMoyennes diff --git a/app/pe/pe_tagtable.py b/app/pe/pe_tabletags.py similarity index 100% rename from app/pe/pe_tagtable.py rename to app/pe/pe_tabletags.py diff --git a/app/pe/pe_tools.py b/app/pe/pe_tools.py deleted file mode 100644 index c5ada421..00000000 --- a/app/pe/pe_tools.py +++ /dev/null @@ -1,1126 +0,0 @@ -# -*- mode: python -*- -# -*- coding: utf-8 -*- - -############################################################################## -# -# 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 os -import datetime -import re -import unicodedata - - -from flask import g - -import app.scodoc.sco_utils as scu -from app import log -from app.models import FormSemestre -from app.scodoc import sco_formsemestre -from app.scodoc.sco_logos import find_logo - -PE_DEBUG = 1 - -if not PE_DEBUG: - # log to notes.log - def pe_print(*a, **kw): - # kw is ignored. log always add a newline - log(" ".join(a)) - -else: - pe_print = print # print function - - -# Generated LaTeX files are encoded as: -PE_LATEX_ENCODING = "utf-8" - -# /opt/scodoc/tools/doc_poursuites_etudes -REP_DEFAULT_AVIS = os.path.join(scu.SCO_TOOLS_DIR, "doc_poursuites_etudes/") -REP_LOCAL_AVIS = os.path.join(scu.SCODOC_CFG_DIR, "doc_poursuites_etudes/") - -PE_DEFAULT_AVIS_LATEX_TMPL = REP_DEFAULT_AVIS + "distrib/modeles/un_avis.tex" -PE_LOCAL_AVIS_LATEX_TMPL = REP_LOCAL_AVIS + "local/modeles/un_avis.tex" -PE_DEFAULT_FOOTER_TMPL = REP_DEFAULT_AVIS + "distrib/modeles/un_footer.tex" -PE_LOCAL_FOOTER_TMPL = REP_LOCAL_AVIS + "local/modeles/un_footer.tex" - -# ---------------------------------------------------------------------------------------- - -""" -Descriptif d'un parcours classique BUT - -TODO:: A améliorer si BUT en moins de 6 semestres -""" - -PARCOURS = { - "S1": { - "aggregat": ["S1"], - "ordre": 1, - "affichage_court": "S1", - "affichage_long": "Semestre 1", - }, - "S2": { - "aggregat": ["S2"], - "ordre": 2, - "affichage_court": "S2", - "affichage_long": "Semestre 2", - }, - "1A": { - "aggregat": ["S1", "S2"], - "ordre": 3, - "affichage_court": "1A", - "affichage_long": "1ère année", - }, - "S3": { - "aggregat": ["S3"], - "ordre": 4, - "affichage_court": "S3", - "affichage_long": "Semestre 3", - }, - "S4": { - "aggregat": ["S4"], - "ordre": 5, - "affichage_court": "S4", - "affichage_long": "Semestre 4", - }, - "2A": { - "aggregat": ["S3", "S4"], - "ordre": 6, - "affichage_court": "2A", - "affichage_long": "2ème année", - }, - "3S": { - "aggregat": ["S1", "S2", "S3"], - "ordre": 7, - "affichage_court": "S1+S2+S3", - "affichage_long": "BUT du semestre 1 au semestre 3", - }, - "4S": { - "aggregat": ["S1", "S2", "S3", "S4"], - "ordre": 8, - "affichage_court": "BUT", - "affichage_long": "BUT du semestre 1 au semestre 4", - }, - "S5": { - "aggregat": ["S5"], - "ordre": 9, - "affichage_court": "S5", - "affichage_long": "Semestre 5", - }, - "S6": { - "aggregat": ["S6"], - "ordre": 10, - "affichage_court": "S6", - "affichage_long": "Semestre 6", - }, - "3A": { - "aggregat": ["S5", "S6"], - "ordre": 11, - "affichage_court": "3A", - "affichage_long": "3ème année", - }, - "5S": { - "aggregat": ["S1", "S2", "S3", "S4", "S5"], - "ordre": 12, - "affichage_court": "S1+S2+S3+S4+S5", - "affichage_long": "BUT du semestre 1 au semestre 5", - }, - "6S": { - "aggregat": ["S1", "S2", "S3", "S4", "S5", "S6"], - "ordre": 13, - "affichage_court": "BUT", - "affichage_long": "BUT (tout semestre inclus)", - }, -} -NBRE_SEMESTRES_DIPLOMANT = 6 -AGGREGAT_DIPLOMANT = ( - "6S" # aggrégat correspondant à la totalité des notes pour le diplôme -) -TOUS_LES_SEMESTRES = PARCOURS[AGGREGAT_DIPLOMANT]["aggregat"] -TOUS_LES_AGGREGATS = [cle for cle in PARCOURS.keys() if not cle.startswith("S")] -TOUS_LES_PARCOURS = list(PARCOURS.keys()) - -# ---------------------------------------------------------------------------------------- -def calcul_age(born): - """Calcule l'age à partir de la date de naissance sous forme d'une chaine de caractère 'jj/mm/aaaa'. - Aucun test de validité sur le format de la date n'est fait. - """ - if not isinstance(born, str) or born == "": - return "" - - donnees = born.split("/") - naissance = datetime.datetime(int(donnees[2]), int(donnees[1]), int(donnees[0])) - today = datetime.date.today() - return ( - today.year - - naissance.year - - ((today.month, today.day) < (naissance.month, naissance.day)) - ) - - -def remove_accents(input_unicode_str): - """Supprime les accents d'une chaine unicode""" - nfkd_form = unicodedata.normalize("NFKD", input_unicode_str) - only_ascii = nfkd_form.encode("ASCII", "ignore") - return only_ascii - - -def escape_for_latex(s): - """Protège les caractères pour inclusion dans du source LaTeX""" - if not s: - return "" - conv = { - "&": r"\&", - "%": r"\%", - "$": r"\$", - "#": r"\#", - "_": r"\_", - "{": r"\{", - "}": r"\}", - "~": r"\textasciitilde{}", - "^": r"\^{}", - "\\": r"\textbackslash{}", - "<": r"\textless ", - ">": r"\textgreater ", - } - exp = re.compile( - "|".join( - re.escape(key) - for key in sorted(list(conv.keys()), key=lambda item: -len(item)) - ) - ) - return exp.sub(lambda match: conv[match.group()], s) - - -# ---------------------------------------------------------------------------------------- -def list_directory_filenames(path): - """List of regular filenames in a directory (recursive) - Excludes files and directories begining with . - """ - R = [] - 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 - - -def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip): - """Read pathname server file and add content to zip under path_in_zip""" - rooted_path_in_zip = os.path.join(ziproot, path_in_zip) - zipfile.write(filename=pathname, arcname=rooted_path_in_zip) - # data = open(pathname).read() - # zipfile.writestr(rooted_path_in_zip, data) - - -def add_refs_to_register(register, directory): - """Ajoute les fichiers trouvés dans directory au registre (dictionaire) sous la forme - filename => pathname - """ - length = len(directory) - for pathname in list_directory_filenames(directory): - filename = pathname[length + 1 :] - register[filename] = pathname - - -def add_pe_stuff_to_zip(zipfile, ziproot): - """Add auxiliary files to (already opened) zip - Put all local files found under config/doc_poursuites_etudes/local - and config/doc_poursuites_etudes/distrib - If a file is present in both subtrees, take the one in local. - - Also copy logos - """ - register = {} - # first add standard (distrib references) - distrib_dir = os.path.join(REP_DEFAULT_AVIS, "distrib") - add_refs_to_register(register=register, directory=distrib_dir) - # then add local references (some oh them may overwrite distrib refs) - local_dir = os.path.join(REP_LOCAL_AVIS, "local") - add_refs_to_register(register=register, directory=local_dir) - # at this point register contains all refs (filename, pathname) to be saved - for filename, pathname in register.items(): - add_local_file_to_zip(zipfile, ziproot, pathname, "avis/" + filename) - - # Logos: (add to logos/ directory in zip) - logos_names = ["header", "footer"] - for name in logos_names: - logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id) - if logo is not None: - add_local_file_to_zip( - zipfile, ziproot, logo.filepath, "avis/logos/" + logo.filename - ) - - -# ---------------------------------------------------------------------------------------- -def get_annee_diplome_semestre(sem_base, nbre_sem_formation=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). - - **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 : - - * S5 débutant en 2025 finissant en 2026 : diplome en 2026 - * S3 debutant en 2025 et finissant en 2026 : diplome en 2027 - - La fonction est adaptée au cas des semestres décalés. - - Par exemple : - - * S5 décalé débutant en 2025 et finissant en 2025 : diplome en 2026 - * S3 décalé débutant en 2025 et finissant en 2025 : diplome en 2027 - - Args: - sem_base: Le semestre à partir duquel est prédit l'année de diplomation, soit : - - * un ``FormSemestre`` (Scodoc9) - * un dict (format compatible avec Scodoc7) - - nbre_sem_formation: Le nombre de semestre prévu dans la formation (par défaut 6 pour un BUT) - """ - - if isinstance(sem_base, FormSemestre): - sem_id = sem_base.semestre_id - annee_fin = sem_base.date_fin.year - annee_debut = sem_base.date_debut.year - else: # sem_base est un dictionnaire (Scodoc 7) - sem_id = sem_base["semestre_id"] - annee_fin = int(sem_base["annee_fin"]) - annee_debut = int(sem_base["annee_debut"]) - if ( - 1 <= sem_id <= nbre_sem_formation - ): # Si le semestre est un semestre BUT => problème si formation BUT en 1 an ?? - nbreSemRestant = ( - 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é - # 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 - increment = decalage * (1 - delta) - return annee_fin + nbreAnRestant + increment - - -def get_cosemestres_diplomants(annee_diplome: int, formation_id: int) -> list: - """Ensemble des cosemestres donnant lieu à diplomation à l'``annee_diplome`` - et s'intégrant à la formation donnée par son ``formation_id``. - - **Définition** : Un co-semestre est un semestre : - - * dont l'année de diplômation prédite (sans redoublement) est la même - * dont la formation est la même (optionnel) - * qui a des étudiants inscrits - - Si formation_id == None, ne prend pas en compte l'identifiant de formation - TODO:: A raccrocher à un programme - - Args: - annee_diplome: L'année de diplomation - formation_id: L'identifiant de la formation - """ - tousLesSems = ( - sco_formsemestre.do_formsemestre_list() - ) # tous les semestres memorisés dans scodoc - - if formation_id: - cosemestres_fids = { - sem["id"] - for sem in tousLesSems - if get_annee_diplome_semestre(sem) == annee_diplome - and sem["formation_id"] == formation_id - } - else: - cosemestres_fids = { - sem["id"] - for sem in tousLesSems - if get_annee_diplome_semestre(sem) == annee_diplome - } - - cosemestres = {} - for fid in cosemestres_fids: - cosem = FormSemestre.get_formsemestre(fid) - if len(cosem.etuds_inscriptions) > 0: - cosemestres[fid] = cosem - - return cosemestres - - -# ---------------------------------------------------------------------------------------- -# Variable pour le debug des avislatex (en squeezant le calcul du jury souvent long) -JURY_SYNTHESE_POUR_DEBUG = { - "EID1810": { - "nom": "ROUX", - "entree": "2016", - "civilite_str": "M.", - "promo": 2016, - "S2": { - "groupe": { - "informatique": ( - 13.184230769230767, - 0.21666666666666667, - "18", - 78, - 9.731491508491509, - 18.46846153846154, - 18.46846153846154, - ), - "technique": ( - 12.975409073359078, - 0.6166666666666666, - "16", - 78, - 9.948540264387688, - 18.29285714285714, - 18.29285714285714, - ), - "pe": ( - 12.016584900684544, - 1.116666666666667, - "20", - 78, - 9.83147528118408, - 17.691755169172936, - 17.691755169172936, - ), - "mathematiques": ( - 12.25, - 0.1, - "15 ex", - 78, - 8.45153073717949, - 19.0625, - 19.0625, - ), - "dut": ( - 12.43750128724589, - 1.0, - "19", - 78, - 10.151630181286441, - 17.881104750512645, - 17.881104750512645, - ), - }, - "promo": { - "informatique": ( - 13.184230769230767, - 0.21666666666666667, - "25", - 73, - 11.696187214611871, - 18.51346153846154, - 18.51346153846154, - ), - "technique": ( - 12.975409073359078, - 0.6166666666666666, - "23", - 73, - 11.862307379173147, - 17.616047267953675, - 17.616047267953675, - ), - "pe": ( - 12.016584900684544, - 1.116666666666667, - "28", - 73, - 11.571004424603757, - 16.706338951857248, - 16.706338951857248, - ), - "mathematiques": ( - 12.25, - 0.1, - "18 ex", - 73, - 10.00886454908676, - 19.0625, - 19.0625, - ), - "dut": ( - 12.43750128724589, - 1.0, - "25", - 73, - 11.88798432763965, - 17.397627309377608, - 17.397627309377608, - ), - }, - }, - "S1": { - "groupe": { - "informatique": ( - 16.064999999999998, - 0.16666666666666669, - "11", - 82, - 11.020296296296294, - 19.325999999999997, - 19.325999999999997, - ), - "technique": ( - 14.513007894736845, - 0.6333333333333333, - "11", - 82, - 11.195082967479676, - 18.309764912280702, - 18.309764912280702, - ), - "pe": ( - 13.260301515151516, - 1.1, - "19", - 82, - 10.976036277232245, - 17.7460505050505, - 17.7460505050505, - ), - "mathematiques": ( - 11.142850000000001, - 0.13333333333333333, - "34", - 82, - 10.314605121951217, - 19.75, - 19.75, - ), - "dut": ( - 13.54367375, - 1.0, - "19", - 82, - 11.22193801880508, - 18.226902529333334, - 18.226902529333334, - ), - }, - "promo": { - "informatique": ( - 16.064999999999998, - 0.16666666666666669, - "15", - 73, - 13.265276712328768, - 19.325999999999997, - 19.325999999999997, - ), - "technique": ( - 14.513007894736845, - 0.6333333333333333, - "16", - 73, - 12.996048795361693, - 18.309764912280702, - 18.309764912280702, - ), - "pe": ( - 13.260301515151516, - 1.1, - "25", - 73, - 12.4107195879539, - 17.7460505050505, - 17.7460505050505, - ), - "mathematiques": ( - 11.142850000000001, - 0.13333333333333333, - "39", - 73, - 11.320606952054794, - 19.75, - 19.75, - ), - "dut": ( - 13.54367375, - 1.0, - "25", - 73, - 12.730581289342638, - 18.226902529333334, - 18.226902529333334, - ), - }, - }, - "4S": { - "groupe": { - "informatique": ( - 14.84359375, - 0.5333333333333333, - "2", - 19, - 10.69933552631579, - 18.28646875, - 18.28646875, - ), - "pe": ( - 12.93828572598162, - 3.75, - "4", - 19, - 11.861967145815218, - 15.737718967605682, - 15.737718967605682, - ), - "mathematiques": (None, None, "1 ex", 19, None, None, None), - "ptut": (None, None, "1 ex", 19, None, None, None), - "dut": ( - 13.511767410105122, - 4.0, - "4", - 19, - 12.573349864933606, - 15.781651391587998, - 15.781651391587998, - ), - }, - "promo": { - "informatique": ( - 16.075, - 0.1, - "4", - 73, - 10.316541095890413, - 19.333333333333336, - 19.333333333333336, - ), - "pe": ( - 13.52416666666667, - 0.49999999999999994, - "13", - 73, - 11.657102668465479, - 16.853208080808084, - 16.853208080808084, - ), - "mathematiques": ( - None, - None, - "55 ex", - 73, - 7.705091805555555, - 19.8, - 19.8, - ), - "dut": ( - 14.425416666666665, - 1.0, - "12", - 73, - 13.188168241098825, - 16.612613522048612, - 16.612613522048612, - ), - }, - }, - "S4": { - "groupe": { - "informatique": ( - 16.075, - 0.1, - "1", - 19, - 8.799078947368422, - 16.075, - 16.075, - ), - "technique": ( - 13.835576923076923, - 0.4333333333333333, - "4", - 19, - 12.238304655870447, - 16.521153846153847, - 16.521153846153847, - ), - "pe": ( - 13.52416666666667, - 0.49999999999999994, - "4", - 19, - 12.292846491228072, - 16.25833333333334, - 16.25833333333334, - ), - "dut": ( - 14.425416666666665, - 1.0, - "6", - 19, - 13.628367861842106, - 15.267566666666665, - 15.267566666666665, - ), - }, - "promo": { - "informatique": ( - 16.075, - 0.1, - "4", - 73, - 10.316541095890413, - 19.333333333333336, - 19.333333333333336, - ), - "pe": ( - 13.52416666666667, - 0.49999999999999994, - "13", - 73, - 11.657102668465479, - 16.853208080808084, - 16.853208080808084, - ), - "technique": ( - 13.835576923076923, - 0.4333333333333333, - "11", - 73, - 12.086685508009952, - 17.25909420289855, - 17.25909420289855, - ), - "mathematiques": ( - None, - None, - "55 ex", - 73, - 7.705091805555555, - 19.8, - 19.8, - ), - "ptut": ( - 13.5, - 0.13333333333333333, - "50", - 73, - 13.898173515981734, - 17.083333333333332, - 17.083333333333332, - ), - "dut": ( - 14.425416666666665, - 1.0, - "12", - 73, - 13.188168241098825, - 16.612613522048612, - 16.612613522048612, - ), - }, - }, - "1A": { - "groupe": { - "informatique": ( - 14.43673913043478, - 0.38333333333333336, - "16", - 78, - 11.046040002787066, - 18.85992173913043, - 18.85992173913043, - ), - "technique": ( - 13.754459142857144, - 1.25, - "14", - 78, - 11.179785631638866, - 18.493250340136054, - 18.493250340136054, - ), - "pe": ( - 12.633767581547854, - 2.216666666666667, - "18", - 78, - 10.912253971396854, - 18.39547581699347, - 18.39547581699347, - ), - "mathematiques": ( - 11.617342857142857, - 0.23333333333333334, - "24", - 78, - 9.921286855287565, - 19.375000000000004, - 19.375000000000004, - ), - "dut": ( - 12.990587518622945, - 2.0, - "18", - 78, - 11.2117147027821, - 18.391345156695156, - 18.391345156695156, - ), - }, - "promo": { - "informatique": ( - 13.184230769230767, - 0.21666666666666667, - "25", - 73, - 11.696187214611871, - 18.51346153846154, - 18.51346153846154, - ), - "technique": ( - 12.975409073359078, - 0.6166666666666666, - "23", - 73, - 11.862307379173147, - 17.616047267953675, - 17.616047267953675, - ), - "pe": ( - 12.016584900684544, - 1.116666666666667, - "28", - 73, - 11.571004424603757, - 16.706338951857248, - 16.706338951857248, - ), - "mathematiques": ( - 12.25, - 0.1, - "18 ex", - 73, - 10.00886454908676, - 19.0625, - 19.0625, - ), - "dut": ( - 12.43750128724589, - 1.0, - "25", - 73, - 11.88798432763965, - 17.397627309377608, - 17.397627309377608, - ), - }, - }, - "2A": { - "groupe": { - "informatique": ( - 15.88333333333333, - 0.15000000000000002, - "2", - 19, - 9.805818713450288, - 17.346666666666668, - 17.346666666666668, - ), - "pe": ( - 13.378513043478259, - 1.5333333333333334, - "6", - 19, - 12.099566454042717, - 16.06209927536232, - 16.06209927536232, - ), - "technique": ( - 13.965093333333336, - 1.1666666666666665, - "5", - 19, - 12.51068332957394, - 16.472092380952386, - 16.472092380952386, - ), - "mathematiques": (None, None, "1 ex", 19, None, None, None), - "dut": ( - 14.032947301587301, - 2.0, - "4", - 19, - 13.043386086541773, - 15.574706269841268, - 15.574706269841268, - ), - }, - "promo": { - "informatique": ( - 16.075, - 0.1, - "4", - 73, - 10.316541095890413, - 19.333333333333336, - 19.333333333333336, - ), - "pe": ( - 13.52416666666667, - 0.49999999999999994, - "13", - 73, - 11.657102668465479, - 16.853208080808084, - 16.853208080808084, - ), - "technique": ( - 13.835576923076923, - 0.4333333333333333, - "11", - 73, - 12.086685508009952, - 17.25909420289855, - 17.25909420289855, - ), - "mathematiques": ( - None, - None, - "55 ex", - 73, - 7.705091805555555, - 19.8, - 19.8, - ), - "dut": ( - 14.425416666666665, - 1.0, - "12", - 73, - 13.188168241098825, - 16.612613522048612, - 16.612613522048612, - ), - }, - }, - "nbSemestres": 4, - "code_nip": "21414563", - "prenom": "Baptiste", - "age": "21", - "lycee": "PONCET", - "3S": { - "groupe": { - "informatique": ( - 14.559423076923077, - 0.43333333333333335, - "3", - 19, - 11.137856275303646, - 18.8095, - 18.8095, - ), - "pe": ( - 12.84815019664546, - 3.25, - "4", - 19, - 11.795678015751701, - 15.657624449801428, - 15.657624449801428, - ), - "technique": ( - 13.860638395358142, - 1.9833333333333334, - "3", - 19, - 12.395950358235925, - 17.340302131732695, - 17.340302131732695, - ), - "mathematiques": ( - 11.494044444444445, - 0.3, - "6", - 19, - 9.771571754385965, - 14.405358333333334, - 14.405358333333334, - ), - "dut": ( - 13.207217657917942, - 3.0, - "4", - 19, - 12.221677199297439, - 15.953012966561774, - 15.953012966561774, - ), - }, - "promo": { - "informatique": (15.5, 0.05, "13", 73, 10.52222222222222, 20.0, 20.0), - "pe": ( - 13.308035483870967, - 1.0333333333333334, - "17", - 73, - 11.854843423685786, - 16.191317607526884, - 16.191317607526884, - ), - "technique": ( - 14.041625757575758, - 0.7333333333333333, - "10", - 73, - 11.929466899200335, - 16.6400384469697, - 16.6400384469697, - ), - "mathematiques": ( - 11.0625, - 0.06666666666666667, - "40", - 73, - 11.418430205479451, - 19.53, - 19.53, - ), - "dut": ( - 13.640477936507937, - 1.0, - "14", - 73, - 12.097377866597594, - 16.97088994741667, - 16.97088994741667, - ), - }, - }, - "bac": "STI2D", - "S3": { - "groupe": { - "informatique": (15.5, 0.05, "5", 19, 12.842105263157896, 20.0, 20.0), - "pe": ( - 13.308035483870967, - 1.0333333333333334, - "8", - 19, - 12.339608902093943, - 15.967147311827956, - 15.967147311827956, - ), - "technique": ( - 14.041625757575758, - 0.7333333333333333, - "7", - 19, - 13.128539816586922, - 16.44310151515152, - 16.44310151515152, - ), - "mathematiques": ( - 11.0625, - 0.06666666666666667, - "6", - 19, - 9.280921052631578, - 16.125, - 16.125, - ), - "dut": ( - 13.640477936507937, - 1.0, - "8", - 19, - 12.83638061385213, - 15.881845873015871, - 15.881845873015871, - ), - }, - "promo": { - "informatique": (15.5, 0.05, "13", 73, 10.52222222222222, 20.0, 20.0), - "pe": ( - 13.308035483870967, - 1.0333333333333334, - "17", - 73, - 11.854843423685786, - 16.191317607526884, - 16.191317607526884, - ), - "technique": ( - 14.041625757575758, - 0.7333333333333333, - "10", - 73, - 11.929466899200335, - 16.6400384469697, - 16.6400384469697, - ), - "mathematiques": ( - 11.0625, - 0.06666666666666667, - "40", - 73, - 11.418430205479451, - 19.53, - 19.53, - ), - "dut": ( - 13.640477936507937, - 1.0, - "14", - 73, - 12.097377866597594, - 16.97088994741667, - 16.97088994741667, - ), - }, - }, - "parcours": [ - { - "nom_semestre_dans_parcours": "semestre 4 FAP 2016", - "titreannee": "DUT RT UFA (PPN 2013), semestre 4 FAP 2016", - }, - { - "nom_semestre_dans_parcours": "semestre 3 FAP 2015-2016", - "titreannee": "DUT RT UFA (PPN 2013), semestre 3 FAP 2015-2016", - }, - { - "nom_semestre_dans_parcours": "semestre 2 FI 2015", - "titreannee": "DUT RT, semestre 2 FI 2015", - }, - { - "nom_semestre_dans_parcours": "semestre 1 FI 2014-2015", - "titreannee": "DUT RT, semestre 1 FI 2014-2015", - }, - ], - } -} diff --git a/app/pe/pe_trajectoire.py b/app/pe/pe_trajectoire.py index 415fee24..d527de3c 100644 --- a/app/pe/pe_trajectoire.py +++ b/app/pe/pe_trajectoire.py @@ -1,4 +1,4 @@ -import app.pe.pe_tools as pe_tools +import app.pe.pe_comp as pe_tools from app.models import FormSemestre from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date diff --git a/app/pe/pe_trajectoiretag.py b/app/pe/pe_trajectoiretag.py index abefa008..de9449c2 100644 --- a/app/pe/pe_trajectoiretag.py +++ b/app/pe/pe_trajectoiretag.py @@ -38,18 +38,17 @@ Created on Fri Sep 9 09:15:05 2016 from app.comp import moy_sem from app.comp.res_sem import load_formsemestre_results -from app.models import FormSemestre -from app.pe.pe_semestretag import SemestreTag -from app.pe import pe_tagtable +from app.pe.pe_semtag import SemestreTag +from app.pe import pe_tabletags import pandas as pd import numpy as np from app.pe.pe_trajectoire import Trajectoire from app.pe.pe_etudiant import EtudiantsJuryPE -from app.pe.pe_trajectoire import TrajectoiresJuryPE +from app.pe.pe_tabletags import TableTag -class TrajectoireTag(pe_tagtable.TableTag): +class TrajectoireTag(TableTag): """Calcule les moyennes par tag d'une combinaison de semestres (trajectoires), identifiée par un nom d'aggrégat (par ex: '3S') et par un semestre terminal, pour extraire les classements par tag pour un @@ -69,7 +68,7 @@ class TrajectoireTag(pe_tagtable.TableTag): donnees_etudiants: EtudiantsJuryPE, ): """ """ - pe_tagtable.TableTag.__init__(self, nom=nom) + TableTag.__init__(self, nom=nom) """La trajectoire associée""" self.trajectoire_id = trajectoire.trajectoire_id