# -*- 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 Formation, FormSemestre from app.scodoc.gen_tables import GenTable, SeqGenTable import app.scodoc.sco_utils as scu from app.scodoc import codes_cursus # codes_cursus.NEXT -> sem suivant from app.scodoc import sco_etud from app.scodoc import sco_formsemestre from app.pe import pe_tagtable from app.pe import pe_tools from app.pe import pe_semestretag from app.pe import pe_settag # ---------------------------------------------------------------------------------------- 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'annee d'obtention du diplome BUT et du jury de PE (generalement fevrier 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 NBRE_SEMESTRES_PARCOURS = 6 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)", }, } AGGREGAT_DIPLOMANT = "6S" # aggrégat correspondant à la totalité des notes pour le diplôme TOUS_LES_SEMESTRES = PARCOURS["6S"]["aggregat"] TOUS_LES_AGGREGATS = [cle for cle in PARCOURS.keys() if not cle.startswith("S")] TOUS_LES_PARCOURS = list(PARCOURS.keys()) # ------------------------------------------------------------------------------------------------------------------ def __init__(self, sem_base: FormSemestre, semBase # CB: à supprimer à long terme ): """ 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.semTagDict = ( {} ) # Les semestres taggués à la base des calculs de moyenne par tag self.setTagDict = ( {} ) # dictionnaire récapitulant les semTag impliqués dans le jury de la forme { 'formsemestre_id' : object Semestre_tag self.promoTagDict = {} # L'année du diplome self.diplome = get_annee_diplome_semestre(semBase) # 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.ETUDINFO_DICT = {} # Les infos sur les étudiants self.PARCOURSINFO_DICT = {} # Les parcours des étudiants self.syntheseJury = {} # Le jury de synthèse self.semestresDeScoDoc = sco_formsemestre.do_formsemestre_list() # Calcul du jury PE self.exe_calculs_juryPE(semBase) 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 filename = self.NOM_EXPORT_ZIP + "_jurySyntheseDict" + scu.XLSX_SUFFIX self.xlsV2 = self.table_syntheseJury(mode="multiplesheet") if self.xlsV2: self.add_file_to_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, data, path=""): """Add a file to our zip All files under NOM_EXPORT_ZIP/ path may specify a subdirectory """ 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 # **************************************************************************************************************** # # Lancement des différentes actions permettant le calcul du jury PE # **************************************************************************************************************** # def exe_calculs_juryPE(self, semBase): # Liste des étudiants à traiter pour identifier ceux qui seront diplômés if pe_tools.PE_DEBUG: pe_tools.pe_print( "*** Recherche et chargement des étudiants diplômés en %d" % (self.diplome) ) self.get_etudiants_in_jury( semBase, avec_meme_formation=False ) # calcul des coSemestres # Les semestres impliqués (ceux valides pour les étudiants à traiter) # ------------------------------------------------------------------- if pe_tools.PE_DEBUG: pe_tools.pe_print("*** Création des semestres taggués") self.get_semtags_in_jury() if pe_tools.PE_DEBUG: for semtag in self.semTagDict.values(): # Export filename = self.NOM_EXPORT_ZIP + semtag.nom + ".csv" self.zipfile.writestr(filename, semtag.str_tagtable()) # self.export_juryPEDict() # Les moyennes sur toute la scolarité # ----------------------------------- if pe_tools.PE_DEBUG: pe_tools.pe_print( "*** Création des moyennes sur différentes combinaisons de semestres et différents groupes d'étudiant" ) self.get_settags_in_jury() if pe_tools.PE_DEBUG: for settagdict in self.setTagDict.values(): # Export for settag in settagdict.values(): filename = self.NOM_EXPORT_ZIP + semtag.nom + ".csv" self.zipfile.writestr(filename, semtag.str_tagtable()) # self.export_juryPEDict() # Les interclassements # -------------------- if pe_tools.PE_DEBUG: pe_tools.pe_print( "*** Création des interclassements au sein de la promo sur différentes combinaisons de semestres" ) self.get_promotags_in_jury() # **************************************************************************************************************** # # Fonctions relatives à la liste des étudiants à prendre en compte dans le jury # **************************************************************************************************************** # # ------------------------------------------------------------------------------------------------------------------ def get_etudiants_in_jury(self, semBase, avec_meme_formation=False): """ Calcule la liste des étudiants à prendre en compte dans le jury et la renvoie sous la forme """ # Les cosemestres donnant lieu à meme année de diplome coSems = get_cosemestres_diplomants( semBase, avec_meme_formation=avec_meme_formation ) # calcul des coSemestres if pe_tools.PE_DEBUG: pe_tools.pe_print( "1) Recherche des coSemestres -> %d trouvés" % len(coSems) ) # Les étudiants inscrits dans les cosemestres if pe_tools.PE_DEBUG: pe_tools.pe_print("2) Liste des étudiants dans les différents co-semestres") listEtudId = self.get_etudiants_dans_semestres( coSems ) # étudiants faisant parti des cosemestres if pe_tools.PE_DEBUG: pe_tools.pe_print(" => %d étudiants trouvés" % len(listEtudId)) # L'analyse des parcours étudiants pour déterminer leur année effective de diplome avec prise en compte des redoublements, des abandons, .... if pe_tools.PE_DEBUG: pe_tools.pe_print("3) Analyse des parcours individuels des étudiants") for no_etud, etudid in enumerate(listEtudId): self.add_etudiants(etudid) if pe_tools.PE_DEBUG: if (no_etud + 1) % 10 == 0: pe_tools.pe_print((no_etud + 1), " ", end="") pe_tools.pe_print() if pe_tools.PE_DEBUG: pe_tools.pe_print( " => %d étudiants à diplômer en %d" % (len(self.get_etudids_du_jury()), self.diplome) ) pe_tools.pe_print( " => %d étudiants éliminer pour abandon" % (len(listEtudId) - len(self.get_etudids_du_jury())) ) # ------------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------------ def get_etudiants_dans_semestres(self, semsListe): """Renvoie la liste des etudid des etudiants inscrits à l'un des semestres de la liste fournie en paramètre en supprimant les doublons (i.e. un même étudiant qui apparaîtra 2 fois)""" etudiants = [] for sem in semsListe: # pour chacun des semestres de la liste nt = self.get_cache_notes_d_un_semestre(sem["formsemestre_id"]) etudiantsDuSemestre = ( [ins.etudid for ins in nt.formsemestre.inscriptions] # nt.get_etudids() ) # identification des etudiants du semestre if pe_tools.PE_DEBUG: pe_tools.pe_print( " --> chargement du semestre %s : %d etudiants " % (sem["formsemestre_id"], len(etudiantsDuSemestre)) ) etudiants.extend(etudiantsDuSemestre) return list(set(etudiants)) # suppression des doublons # ------------------------------------------------------------------------------------------------------------------ def get_etudids_du_jury(self, ordre="aucun"): """Renvoie la liste de tous les étudiants (concrètement leur etudid) participant au jury c'est-à-dire, ceux dont la date du 'jury' est self.diplome et n'ayant pas abandonné. Si l'ordre est précisé, donne une liste etudid dont le nom, prenom trié par ordre alphabétique """ etudids = [ etudid for (etudid, donnees) in self.PARCOURSINFO_DICT.items() if donnees["diplome"] == self.diplome and donnees["abandon"] == False ] if ordre == "alphabetique": # Tri alphabétique etudidsAvecNom = [ (etudid, etud["nom"] + "/" + etud["prenom"]) for (etudid, etud) in self.PARCOURSINFO_DICT.items() if etudid in etudids ] etudidsAvecNomTrie = sorted(etudidsAvecNom, key=lambda col: col[1]) etudids = [etud[0] for etud in etudidsAvecNomTrie] return etudids # ------------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------------ def add_etudiants(self, etudid): """Ajoute un étudiant connaissant son etudid au dictionnaire de synthèse jurydict. L'ajout consiste à : * insérer une entrée pour l'étudiant en mémorisant ses infos (get_etudInfo), avec son nom, prénom, etc... * à analyser son parcours, pour vérifier s'il n'a pas abandonné l'IUT en cours de route (cf. clé abandon) * à chercher ses semestres valides (formsemestre_id) et ses années valides (formannee_id), c'est-à-dire ceux pour lesquels il faudra prendre en compte ses notes dans les calculs de moyenne (type 1A=S1+S2/2) Args: etudid: L'etudid d'un étudiant, à ajouter au jury s'il respecte les critères précédents """ if etudid not in self.PARCOURSINFO_DICT: etud = self.get_cache_etudInfo_d_un_etudiant( etudid ) # On charge les données de l'étudiant if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2: pe_tools.pe_print(etud["nom"] + " " + etud["prenom"], end="") self.PARCOURSINFO_DICT[etudid] = { "etudid": etudid, # les infos sur l'étudiant "nom": etud["nom"], # Ajout à la table jury } # Analyse du parcours de l'étudiant # Sa date prévisionnelle de diplome self.PARCOURSINFO_DICT[etudid][ "diplome" ] = self.calcul_anneePromoBUT_d_un_etudiant(etudid) if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2: pe_tools.pe_print( "promo=" + str(self.PARCOURSINFO_DICT[etudid]["diplome"]), end="" ) # Est-il réorienté ou démissionnaire ? self.PARCOURSINFO_DICT[etudid][ "abandon" ] = self.est_un_etudiant_reoriente_ou_demissionnaire(etudid) # A-t-il arrêté de lui-même sa formation avant la fin ? etatD = self.est_un_etudiant_disparu(etudid) if etatD == True: self.PARCOURSINFO_DICT[etudid]["abandon"] = True # dans le jury ne seront traités que les étudiants ayant la date attendue de diplome et n'ayant pas abandonné # Quels sont ses semestres validant (i.e ceux dont les notes doivent être prises en compte pour le jury) # et s'ils existent quelles sont ses notes utiles ? sesFormsemestre_idValidants = [ self.get_Fid_d_un_Si_valide_d_un_etudiant(etudid, nom_sem) for nom_sem in JuryPE.TOUS_LES_SEMESTRES # Recherche du formsemestre_id de son Si valide (ou a défaut en cours) ] for i, nom_sem in enumerate(JuryPE.TOUS_LES_SEMESTRES): fid = sesFormsemestre_idValidants[i] self.PARCOURSINFO_DICT[etudid][nom_sem] = fid # ['formsemestre_id'] if fid != None and pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2: pe_tools.pe_print(nom_sem + "=" + str(fid), end="") # self.get_moyennesEtClassements_par_semestre_d_un_etudiant( etudid, fid ) # Quelles sont ses années validantes ('1A', '2A') et ses parcours (3S, 4S) validants ? for parcours in JuryPE.TOUS_LES_AGGREGATS: lesSemsDuParcours = JuryPE.PARCOURS[parcours]["aggregat"] # les semestres du parcours : par ex. ['S1', 'S2', 'S3'] lesFidsValidantDuParcours = [ sesFormsemestre_idValidants[ JuryPE.TOUS_LES_SEMESTRES.index(nom_sem) ] for nom_sem in lesSemsDuParcours # par ex. ['SEM4532', 'SEM567', ...] ] parcours_incomplet = ( sum([fid == None for fid in lesFidsValidantDuParcours]) > 0 ) if not parcours_incomplet: self.PARCOURSINFO_DICT[etudid][ parcours ] = lesFidsValidantDuParcours[-1] else: self.PARCOURSINFO_DICT[etudid][parcours] = None if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2: pe_tools.pe_print( parcours + "=" + str(self.PARCOURSINFO_DICT[etudid][parcours]), end="", ) # if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2: # print # ------------------------------------------------------------------------------------------------------------------ def est_un_etudiant_reoriente_ou_demissionnaire(self, etudid): """Renvoie True si l'étudiant est réorienté (NAR) ou démissionnaire (DEM)""" from app.scodoc import sco_report reponse = False etud = self.get_cache_etudInfo_d_un_etudiant(etudid) (_, parcours) = sco_report.get_code_cursus_etud(etud) if ( len(codes_cursus.CODES_SEM_REO & set(parcours.values())) > 0 ): # Eliminé car NAR apparait dans le parcours reponse = True if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2: pe_tools.pe_print(" -> à éliminer car réorienté (NAR)") if "DEM" in list(parcours.values()): # Eliminé car DEM reponse = True if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2: pe_tools.pe_print(" -> à éliminer car DEM") return reponse # ------------------------------------------------------------------------------------------------------------------ def est_un_etudiant_disparu(self, etudid): """Renvoie True si l'étudiant n'a pas achevé la formation à l'IUT et a disparu des listes, sans pour autant avoir été indiqué NAR ou DEM ; recherche son dernier semestre validé et regarde s'il n'existe pas parmi les semestres existants dans scodoc un semestre postérieur (en terme de date de début) de n° au moins égal à celui de son dernier semestre valide dans lequel il aurait pu s'inscrire mais ne l'a pas fait.""" sessems = self.get_semestresBUT_d_un_etudiant( etudid ) # les semestres de l'étudiant sonDernierSidValide = self.get_dernier_semestre_id_valide_d_un_etudiant(etudid) sesdates = [ pe_tagtable.conversionDate_StrToDate(sem["date_fin"]) for sem in sessems ] # association 1 date -> 1 semestrePE pour les semestres de l'étudiant if sesdates: lastdate = max(sesdates) # date de fin de l'inscription la plus récente else: return False # if PETable.AFFICHAGE_DEBUG_PE == True : pe_tools.pe_print(" derniere inscription = ", lastDateSem) if sonDernierSidValide is None: # si l'étudiant n'a validé aucun semestre, les prend tous ? (à vérifier) semestresSuperieurs = self.semestresDeScoDoc else: semestresSuperieurs = [ sem for sem in self.semestresDeScoDoc if sem["semestre_id"] > sonDernierSidValide ] # Semestre de rang plus élevé que son dernier sem valide datesDesSemestresSuperieurs = [ pe_tagtable.conversionDate_StrToDate(sem["date_debut"]) for sem in semestresSuperieurs ] datesDesSemestresPossibles = [ date_deb for date_deb in datesDesSemestresSuperieurs if date_deb >= lastdate ] # date de debut des semestres possibles postérieur au dernier semestre de l'étudiant et de niveau plus élevé que le dernier semestre valide de l'étudiant if ( len(datesDesSemestresPossibles) > 0 ): # etudiant ayant disparu de la circulation # if PETable.AFFICHAGE_DEBUG_PE == True : # pe_tools.pe_print(" -> à éliminer car des semestres où il aurait pu s'inscrire existent ") # pe_tools.pe_print(pe_tools.print_semestres_description( datesDesSemestresPossibles.values() )) return True else: return False # ------------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------------ def get_dernier_semestre_id_valide_d_un_etudiant(self, etudid): """Renvoie le n° (semestre_id) du dernier semestre validé par un étudiant fourni par son etudid et None si aucun semestre n'a été validé """ from app.scodoc import sco_report etud = self.get_cache_etudInfo_d_un_etudiant(etudid) (code, parcours) = sco_report.get_code_cursus_etud( etud ) # description = '1234:A', parcours = {1:ADM, 2:NAR, ...} sonDernierSemestreValide = max( [ int(cle) for (cle, code) in parcours.items() if code in codes_cursus.CODES_SEM_VALIDES ] + [0] ) # n° du dernier semestre valide, 0 sinon return sonDernierSemestreValide if sonDernierSemestreValide > 0 else None # ------------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------------ def get_Fid_d_un_Si_valide_d_un_etudiant(self, etudid, nom_semestre): """Récupère le formsemestre_id valide d'un étudiant fourni son etudid à un semestre DUT de n° semestre_id donné. Si le semestre est en cours (pas encore de jury), renvoie le formsemestre_id actuel. """ semestre_id = JuryPE.TOUS_LES_SEMESTRES.index(nom_semestre) + 1 sesSi = self.get_semestresBUT_d_un_etudiant( etudid, semestre_id ) # extrait uniquement les Si par ordre temporel décroissant if len(sesSi) > 0: # S'il a obtenu au moins une note # mT = sesMoyennes[0] leFid = sesSi[0]["formsemestre_id"] for i, sem in enumerate( sesSi ): # Parcours des éventuels semestres précédents nt = self.get_cache_notes_d_un_semestre(sem["formsemestre_id"]) dec = nt.get_etud_decision_sem( etudid ) # quelle est la décision du jury ? if dec and (dec["code"] in codes_cursus.CODES_SEM_VALIDES): # isinstance( sesMoyennes[i+1], float) and # mT = sesMoyennes[i+1] # substitue la moyenne si le semestre suivant est "valide" leFid = sem["formsemestre_id"] else: leFid = None return leFid # **************************************************************************************************************** # # Traitements des semestres impliqués dans le jury # **************************************************************************************************************** # # ------------------------------------------------------------------------------------------------------------------ def get_semtags_in_jury(self): """ Créé les semestres tagués relatifs aux résultats des étudiants à prendre en compte dans le jury. Calcule les moyennes et les classements de chaque semestre par tag et les statistiques de ces semestres. """ lesFids = self.get_formsemestreids_du_jury( self.get_etudids_du_jury(), liste_semestres=JuryPE.TOUS_LES_SEMESTRES ) for i, fid in enumerate(lesFids): if pe_tools.PE_DEBUG: pe_tools.pe_print( "%d) Semestre taggué %s (avec classement dans groupe)" % (i + 1, fid) ) self.add_semtags_in_jury(fid) # ------------------------------------------------------------------------------------------------------------------ def add_semtags_in_jury(self, fid): """Crée si nécessaire un semtag et le mémorise dans self.semTag ; charge également les données des nouveaux étudiants qui en font partis. """ # Semestre taggué avec classement dans le groupe if fid not in self.semTagDict: nt = self.get_cache_notes_d_un_semestre(fid) # Création du semestres self.semTagDict[fid] = pe_semestretag.SemestreTag( nt, nt.sem ) # Création du pesemestre associé self.semTagDict[fid].comp_data_semtag() lesEtudids = self.semTagDict[fid].get_etudids() lesEtudidsManquants = [] for etudid in lesEtudids: if ( etudid not in self.PARCOURSINFO_DICT ): # Si l'étudiant n'a pas été pris en compte dans le jury car déjà diplômé ou redoublant lesEtudidsManquants.append(etudid) # self.get_cache_etudInfo_d_un_etudiant(etudid) self.add_etudiants( etudid ) # Ajoute les élements de parcours de l'étudiant nbinscrit = self.semTagDict[fid].get_nbinscrits() if pe_tools.PE_DEBUG: pe_tools.pe_print( " - %d étudiants classés " % (nbinscrit) + ": " + ",".join( [str(etudid) for etudid in self.semTagDict[fid].get_etudids()] ) ) if lesEtudidsManquants: pe_tools.pe_print( " - dont %d étudiants manquants ajoutés aux données du jury" % (len(lesEtudidsManquants)) + ": " + ", ".join([str(etudid) for etudid in lesEtudidsManquants]) ) pe_tools.pe_print(" - Export csv") filename = self.NOM_EXPORT_ZIP + self.semTagDict[fid].nom + ".csv" self.zipfile.writestr(filename, self.semTagDict[fid].str_tagtable()) # ---------------------------------------------------------------------------------------------------------------- def get_formsemestreids_du_jury(self, etudids, liste_semestres="6S"): """Renvoie la liste des formsemestre_id validants des étudiants en parcourant les semestres valides des étudiants mémorisés dans self.PARCOURSINFO_DICT. Les étudiants sont identifiés par leur etudic donnés dans la liste etudids (généralement self.get_etudids_in_jury() ). La liste_semestres peut être une liste ou une chaine de caractères parmi : * None => tous les Fids validant * 'Si' => le ième 1 semestre * 'iA' => l'année i = ['S1, 'S2'] ou ['S3', 'S4'] * '3S', '4S' => fusion des semestres * [ 'Si', 'iA' , ... ] => une liste combinant les formats précédents """ champs_possibles = list(JuryPE.PARCOURS.keys()) if ( not isinstance(liste_semestres, list) and not isinstance(liste_semestres, str) and liste_semestres not in champs_possibles ): raise ValueError( "Probleme de paramètres d'appel dans pe_jurype.JuryPE.get_formsemestreids_du_jury" ) if isinstance(liste_semestres, list): res = [] for elmt in liste_semestres: res.extend(self.get_formsemestreids_du_jury(etudids, elmt)) return list(set(res)) # si liste_sem est un nom de parcours nom_sem = liste_semestres # if nom_sem in ['1A', '2A', '3S', '4S'] : # return self.get_formsemestreids_du_jury(etudids, JuryPE.PARCOURS[nom_sem] ) # else : fids = { self.PARCOURSINFO_DICT[etudid][nom_sem] for etudid in etudids if self.PARCOURSINFO_DICT[etudid][nom_sem] != None } return list(fids) # **************************************************************************************************************** # # Traitements des parcours impliquées dans le jury # **************************************************************************************************************** # # # ---------------------------------------------------------------------------------------------------------------- # def get_antags_in_jury(self, avec_affichage_debug=True ): # """Construit les settag associés aux années 1A et 2A du jury""" # lesAnnees = {'1A' : ['S1', 'S2'], '2A' : ['S3', 'S4'] } # for nom_annee in lesAnnees: # lesAidDesAnnees = self.get_anneeids_du_jury(annee= nom_annee) # les annee_ids des étudiants du jury # for aid in lesAidDesAnnees: # fidSemTagFinal = JuryPE.convert_aid_en_fid( aid ) # lesEtudisDelAnnee = self.semTagDict[ fidSemTagFinal ].get_etudids() # les etudiants sont ceux inscrits dans le semestre final de l'année # parcoursDesEtudiants = { etudid : self.PARCOURSINFO_DICT[etudid] for etudid in lesEtudisDelAnnee } # les parcours des etudid aka quels semestres sont à prendre en compte # # lesFidsDesEtudiants = self.get_formsemestreids_du_jury(lesEtudisDelAnnee, nom_annee) # les formsemestres_id à prendre en compte pour les moyennes # # Manque-t-il des semtag associés ; si oui, les créé # pe_tools.pe_print(aid, lesFidsDesEtudiants) # for fid in lesFidsDesEtudiants: # self.add_semtags_in_jury(fid, avec_affichage_debug=avec_affichage_debug) # lesSemTagDesEtudiants = { fid: self.semTagDict[fid] for fid in lesFidsDesEtudiants } # # # Tous les semtag nécessaires pour ses étudiants avec ajout éventuel s'ils n'ont pas été chargés # pe_tools.pe_print(" -> Création de l'année tagguée " + str( aid )) # #settag_id, short_name, listeEtudId, groupe, listeSemAAggreger, ParcoursEtudDict, SemTagDict, with_comp_moy=True) # self.anTagDict[ aid ] = pe_settag.SetTag( aid, "Annee " + self.semTagDict[fidSemTagFinal].short_name, \ # lesEtudisDelAnnee, 'groupe', lesAnnees[ nom_annee ], parcoursDesEtudiants, lesSemTagDesEtudiants ) # self.anTagDict[ aid ].comp_data_settag() # calcul les moyennes # **************************************************************************************************************** # # Traitements des moyennes sur différentes combinaisons de parcours 1A, 2A, 3S et 4S, # impliquées dans le jury # **************************************************************************************************************** # def get_settags_in_jury(self): """Calcule les moyennes sur la totalité du parcours (S1 jusqu'à S3 ou S4) en classant les étudiants au sein du semestre final du parcours (même S3, même S4, ...) """ # Par groupe : # combinaisons = { 'S1' : ['S1'], 'S2' : ['S2'], 'S3' : ['S3'], 'S4' : ['S4'], \ # '1A' : ['S1', 'S2'], '2A' : ['S3', 'S4'], # '3S' : ['S1', 'S2', 'S3'], '4S' : ['S1', 'S2', 'S3', 'S4'] } # ---> sur 2 parcours DUT (cas S3 fini, cas S4 fini) for i, nom in enumerate(JuryPE.TOUS_LES_AGGREGATS): parcours = JuryPE.PARCOURS[nom][ "aggregat" ] # La liste des noms de semestres (S1, S2, ...) impliqués dans l'aggrégat # Recherche des parcours possibles par le biais de leur Fid final fids_finaux = self.get_formsemestreids_du_jury( self.get_etudids_du_jury(), nom ) # les formsemestre_ids validant finaux des étudiants du jury if len(fids_finaux) > 0: # S'il existe des parcours validant if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 1: pe_tools.pe_print("%d) Fusion %s avec" % (i + 1, nom)) if nom not in self.setTagDict: self.setTagDict[nom] = {} for fid in fids_finaux: if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 1: pe_tools.pe_print(" - semestre final %s" % (fid)) settag = pe_settag.SetTag( nom, parcours=parcours ) # Le set tag fusionnant les données etudiants = self.semTagDict[ fid ].get_etudids() # Les étudiants du sem final # ajoute les étudiants au semestre settag.set_Etudiants( etudiants, self.PARCOURSINFO_DICT, self.ETUDINFO_DICT, nom_sem_final=self.semTagDict[fid].nom, ) # manque-t-il des semestres ? Si oui, les ajoute au jurype puis au settag for ffid in settag.get_Fids_in_settag(): if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 1: pe_tools.pe_print( " -> ajout du semestre tagué %s" % (ffid) ) self.add_semtags_in_jury(ffid) settag.set_SemTagDict( self.semTagDict ) # ajoute les semestres au settag settag.comp_data_settag() # Calcul les moyennes, les rangs, .. self.setTagDict[nom][fid] = settag # Mémorise le résultat else: if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 1: pe_tools.pe_print("%d) Pas de fusion %s possible" % (i + 1, nom)) def get_promotags_in_jury(self): """Calcule les aggrégats en interclassant les étudiants du jury (les moyennes ont déjà été calculées en amont)""" lesEtudids = self.get_etudids_du_jury() for i, nom in enumerate(JuryPE.PARCOURS.keys()): settag = pe_settag.SetTagInterClasse(nom, diplome=self.diplome) nbreEtudInscrits = settag.set_Etudiants( lesEtudids, self.PARCOURSINFO_DICT, self.ETUDINFO_DICT ) if nbreEtudInscrits > 0: if pe_tools.PE_DEBUG: pe_tools.pe_print( "%d) %s avec interclassement sur la promo" % (i + 1, nom) ) if nom in JuryPE.TOUS_LES_SEMESTRES: settag.set_SetTagDict(self.semTagDict) else: # cas des aggrégats settag.set_SetTagDict(self.setTagDict[nom]) settag.comp_data_settag() self.promoTagDict[nom] = settag else: if pe_tools.PE_DEBUG: pe_tools.pe_print( "%d) Pas d'interclassement %s sur la promo faute de notes" % (i + 1, nom) ) # **************************************************************************************************************** # # Méthodes pour la synthèse du juryPE # ***************************************************************************************************************** def synthetise_juryPE(self): """Synthétise tous les résultats du jury PE dans un dictionnaire""" self.syntheseJury = {} for etudid in self.get_etudids_du_jury(): 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 JuryPE.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.PARCOURSINFO_DICT[etudid][nom] != None ): # Un parcours valide existe if nom in JuryPE.TOUS_LES_SEMESTRES: tagtable = self.semTagDict[self.PARCOURSINFO_DICT[etudid][nom]] else: tagtable = self.setTagDict[nom][ self.PARCOURSINFO_DICT[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.get_semestresBUT_d_un_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 JuryPE.TOUS_LES_PARCOURS: entete += [nom_sem, "descr"] chaine = delim.join(entete) + "\n" for etudid in self.PARCOURSINFO_DICT: donnees = self.PARCOURSINFO_DICT[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 JuryPE.TOUS_LES_PARCOURS: table = ( self.semTagDict[donnees[nom_sem]].nom if donnees[nom_sem] in self.semTagDict 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.get_etudids_du_jury(): 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 JuryPE.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(JuryPE.PARCOURS.keys()) # ['S1', 'S2', ..., '1A', '4S'] # aggregats = sorted( # aggregats, key=lambda t: JuryPE.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 JuryPE.TOUS_LES_PARCOURS: entete.extend(["%s %s" % (sem, champ) for champ in champs]) else: # "singlesheet" allSheets = ["singlesheet"] for sem in JuryPE.TOUS_LES_PARCOURS: # JuryPE.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: # JuryPE.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 """ if etudid not in self.ETUDINFO_DICT: self.ETUDINFO_DICT[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, etudid, semestre_id=None): """Renvoie la liste des semestres DUT d'un étudiant pour un semestre_id (parmi 1,2,3,4) donné en fonction de ses infos d'etud (cf. sco_etud.get_etud_info( etudid=etudid, filled=True)[0]), les semestres étant triés par ordre décroissant. Si semestre_id == None renvoie tous les semestres""" etud = self.get_cache_etudInfo_d_un_etudiant(etudid) nbre_semestres = int(JuryPE.AGGREGAT_DIPLOMANT[0]) # 6 if semestre_id == None: sesSems = [sem for sem in etud["sems"] if 1 <= sem["semestre_id"] <= nbre_semestres] else: sesSems = [sem for sem in etud["sems"] if sem["semestre_id"] == semestre_id] return sesSems # ********************************************** def calcul_anneePromoBUT_d_un_etudiant(self, etudid) -> int: """Calcule et renvoie la date de diplome prévue pour un étudiant fourni avec son etudid en fonction de ses semestres de scolarisation""" semestres = self.get_semestresBUT_d_un_etudiant(etudid) if semestres: return max([get_annee_diplome_semestre(sem) for sem in semestres]) else: return None # ********************************************* # Fonctions d'affichage pour debug def get_resultat_d_un_etudiant(self, etudid): chaine = "" for nom_sem in JuryPE.TOUS_LES_SEMESTRES: semtagid = self.PARCOURSINFO_DICT[etudid][ nom_sem ] # le formsemestre_id du semestre taggué de l'étudiant semtag = self.semTagDict[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 "" # ---------------------------------------------------------------------------------------- # Fonctions # ---------------------------------------------------------------------------------------- def get_annee_diplome_semestre(sem) -> int: """Pour un semestre donne, décrit par le biais du dictionnaire sem usuel : sem = {'formestre_id': ..., 'semestre_id': ..., 'annee_debut': ...} à condition qu'il soit un semestre de formation BUT, predit l'annee à laquelle sera remis le diplome BUT des etudiants scolarisés dans le semestre (en supposant qu'il n'y ait plus de redoublement) et la renvoie sous la forme d'un int. 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'annee 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 * S5 décalé débutant en 2025 et finissant en 2025 => diplome en 2026 * S3 decale débutant en 2025 et finissant en 2025 => diplome en 2027 La règle de calcul utilise l'``annee_fin`` du semestre sur le principe suivant : * nbreSemRestant = nombre de semestres restant avant diplome * nbreAnneeRestant = nombre d'annees restant avant diplome * 1 - delta = 0 si semestre de 1ere partie d'annee / 1 sinon * decalage = active ou désactive un increment à prendre en compte en cas de semestre decale Args: sem: Le semestre """ nbre_semestres = int(JuryPE.AGGREGAT_DIPLOMANT[0]) # 6 if ( 1 <= sem["semestre_id"] <= nbre_semestres ): # Si le semestre est un semestre BUT => problème si formation BUT en 1 an ?? nbreSemRestant = nbre_semestres - sem["semestre_id"] nbreAnRestant = nbreSemRestant // 2 delta = int(sem["annee_fin"]) - int(sem["annee_debut"]) decalage = nbreSemRestant % 2 # 0 si S4, 1 si S3, 0 si S2, 1 si S1 increment = decalage * (1 - delta) return int(sem["annee_fin"]) + nbreAnRestant + increment # ---------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------- def get_cosemestres_diplomants(semBase, avec_meme_formation=False): """Partant d'un semestre de Base = {'formsemestre_id': ..., 'semestre_id': ..., 'annee_debut': ...}, renvoie la liste de tous ses co-semestres (lui-meme inclus) Par co-semestre, s'entend les semestres : > dont l'annee predite pour la remise du diplome DUT est la meme > dont la formation est la même (optionnel) > ne prenant en compte que les etudiants sans redoublement """ tousLesSems = ( sco_formsemestre.do_formsemestre_list() ) # tous les semestres memorisés dans scodoc diplome = get_annee_diplome_semestre(semBase) if avec_meme_formation: # si une formation est imposee nom_formation = str(semBase["formation_id"]) if pe_tools.PE_DEBUG: pe_tools.pe_print(" - avec formation imposée : ", nom_formation) coSems = [ sem for sem in tousLesSems if get_annee_diplome_semestre(sem) == diplome and sem["formation_id"] == semBase["formation_id"] ] else: if pe_tools.PE_DEBUG: pe_tools.pe_print(" - toutes formations confondues") coSems = [ sem for sem in tousLesSems if get_annee_diplome_semestre(sem) == diplome ] return coSems