# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2023 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@univ-paris13.fr # ############################################################################## """Semestres: Codes gestion cursus (constantes) Attention: ne pas confondre avec les "parcours" du BUT. Renommage des anciens "parcours" -> "cursus" effectué en 9.4.41 """ import enum import numpy as np from app import log @enum.unique class CodesCursus(enum.IntEnum): """Codes numériques des cursus (ex parcours), enregistrés en base dans notes_formations.type_parcours Ne pas modifier. """ Legacy = 0 DUT = 100 DUT4 = 110 DUTMono = 120 DUT2 = 130 LP = 200 LP2sem = 210 LP2semEvry = 220 LP2014 = 230 LP2sem2014 = 240 M2 = 250 M2noncomp = 251 Mono = 300 MasterLMD = 402 MasterIG = 403 LicenceUCAC3 = 501 MasterUCAC2 = 502 MonoUCAC = 503 GEN_6_SEM = 600 BUT = 700 ISCID6 = 1001 ISCID4 = 1002 NOTES_TOLERANCE = 0.00499999999999 # si note >= (BARRE-TOLERANCE), considere ok # (permet d'eviter d'afficher 10.00 sous barre alors que la moyenne vaut 9.999) # Barre sur moyenne générale utilisée pour compensations semestres: NOTES_BARRE_GEN = 10.0 NOTES_BARRE_GEN_COMPENSATION = NOTES_BARRE_GEN - NOTES_TOLERANCE # ---------------------------------------------------------------- # Types d'UE: UE_STANDARD = 0 # UE "fondamentale" UE_SPORT = 1 # bonus "sport" UE_STAGE_LP = 2 # ue "projet tuteuré et stage" dans les Lic. Pro. UE_STAGE_10 = 3 # ue "stage" avec moyenne requise > 10 UE_ELECTIVE = 4 # UE "élective" dans certains cursus (UCAC?, ISCID) UE_PROFESSIONNELLE = 5 # UE "professionnelle" (ISCID, ...) UE_OPTIONNELLE = 6 # UE non fondamentales (ILEPS, ...) def ue_is_fondamentale(ue_type): return ue_type in (UE_STANDARD, UE_STAGE_LP, UE_PROFESSIONNELLE) def ue_is_professionnelle(ue_type): return ( ue_type == UE_PROFESSIONNELLE ) # NB: les UE_PROFESSIONNELLE sont à la fois fondamentales et pro UE_TYPE_NAME = { UE_STANDARD: "Standard", UE_SPORT: "Sport/Culture (points bonus)", UE_STAGE_LP: "Projet tuteuré et stage (Lic. Pro.)", UE_STAGE_10: "Stage (moyenne min. 10/20)", UE_ELECTIVE: "Elective (ISCID)", UE_PROFESSIONNELLE: "Professionnelle (ISCID)", UE_OPTIONNELLE: "Optionnelle", # UE_FONDAMENTALE : '"Fondamentale" (eg UCAC)', # UE_OPTIONNELLE : '"Optionnelle" (UCAC)' } # Couleurs RGB (dans [0.,1.]) des UE pour les bulletins: UE_DEFAULT_COLOR = (150 / 255.0, 200 / 255.0, 180 / 255.0) UE_COLORS = { UE_STANDARD: UE_DEFAULT_COLOR, UE_SPORT: (0.40, 0.90, 0.50), UE_STAGE_LP: (0.80, 0.90, 0.90), } UE_SEM_DEFAULT = 1000000 # indice semestre des UE sans modules # ------------------------------------------------------------------ # Codes proposés par ADIUT / Apogee ABAN = "ABAN" ABL = "ABL" ADM = "ADM" # moyenne gen., barres UE, assiduité: sem. validé ADC = "ADC" # admis par compensation (eg moy(S1, S2) > 10) ADSUP = "ADSUP" # BUT: UE ou RCUE validé par niveau supérieur ADJ = "ADJ" # admis par le jury ADJR = "ADJR" # UE admise car son RCUE est ADJ ATT = "ATT" # ATJ = "ATJ" # pb assiduité: décision repoussée au semestre suivant ATB = "ATB" AJ = "AJ" CMP = "CMP" # utile pour UE seulement (indique UE acquise car semestre acquis) DEF = "DEF" # défaillance (n'est pas un code jury dans scodoc mais un état, comme inscrit ou demission) DEM = "DEM" EXCLU = "EXCLU" JSD = "JSD" # jury tenu mais pas de code (Jury Sans Décision) NAR = "NAR" PASD = "PASD" PAS1NCI = "PAS1NCI" RAT = "RAT" # en attente rattrapage, sera ATT dans Apogée RED = "RED" UEBSL = "UEBSL" # UE blanchie # codes actions REDOANNEE = "REDOANNEE" # redouble annee (va en Sn-1) REDOSEM = "REDOSEM" # redouble semestre (va en Sn) RA_OR_NEXT = "RA_OR_NEXT" # redouble annee ou passe en Sn+1 RA_OR_RS = "RA_OR_RS" # redouble annee ou semestre RS_OR_NEXT = "RS_OR_NEXT" # redouble semestre ou passe en Sn+1 NEXT_OR_NEXT2 = "NEXT_OR_NEXT2" # passe en suivant (Sn+1) ou sur-suivant (Sn+2) NEXT = "NEXT" NEXT2 = "NEXT2" # passe au sur-suivant (Sn+2) REO = "REO" BUG = "BUG" ALL = "ALL" # Explication des codes (de semestre ou d'UE) CODES_EXPL = { ABAN: "Non évalué pour manque d’assiduité: non présentation des notes de l'étudiant au jury", ABL: "Année blanche", ADC: "Validé par compensation", ADJ: "Validé par le Jury", ADJR: "UE validée car son RCUE est validé ADJ par le jury", ADM: "Validé", ADSUP: "UE ou RCUE validé car le niveau supérieur est validé", AJ: "Ajourné (ou UE/BC de BUT en attente pour problème de moyenne)", ATB: "Décision en attente d'un autre semestre (au moins une UE sous la barre)", ATJ: "Décision en attente d'un autre semestre (assiduité insuffisante)", ATT: "Décision en attente d'un autre semestre (faute d'atteindre la moyenne)", CMP: """Code UE acquise car semestre acquis, ou, en BUT, acquise par compensation UE avec l’UE de même compétence et de même année (ECTS acquis). Utilisé aussi pour les blocs de compétences BUT (RCUE). """, DEF: "Défaillant, pas ou peu de notes par arrêt de la formation. Non évalué par manque assiduité.", DEM: "Démission", EXCLU: "Exclusion: décision réservée à des décisions disciplinaires", NAR: "Non admis, réorientation, non autorisé à redoubler", PASD: """Année BUT: non admis, mais passage de droit: Passage en Année Supérieure de Droit (+ de 50% des UE VAL et RCUE Ajourné(s) >=8) """, PAS1NCI: """Année BUT: Non admis, mais passage par décision de jury: Passage en Année Supérieure avec au moins 1 Niveau de Compétence Insuffisant (RCUE<8) """, RAT: "En attente d'un rattrapage", RED: "Année: Ajourné, mais autorisé à redoubler", UEBSL: "UE blanchie", } # Les codes de semestres: CODES_JURY_SEM = {ADC, ADJ, ADM, AJ, ATB, ATJ, ATT, DEF, NAR, RAT} CODES_SEM_VALIDES_DE_DROIT = {ADM, ADC} CODES_SEM_VALIDES = CODES_SEM_VALIDES_DE_DROIT | {ADJ} # semestre validé CODES_SEM_ATTENTES = {ATT, ATB, ATJ} # semestre en attente CODES_SEM_REO = {NAR} # reorientation # Les codes d'UEs CODES_JURY_UE = {ADM, CMP, ADJ, ADJR, ADSUP, AJ, ATJ, RAT, DEF, ABAN, DEM, UEBSL} CODES_UE_VALIDES_DE_DROIT = {ADM, CMP} # validation "de droit" CODES_UE_VALIDES = CODES_UE_VALIDES_DE_DROIT | {ADJ, ADJR, ADSUP} "UE validée" CODES_UE_CAPITALISANTS = {ADM} "UE capitalisée" CODES_JURY_RCUE = {ADM, ADJ, ADSUP, CMP, AJ, ATJ, RAT, DEF, ABAN} "codes de jury utilisables sur les RCUEs" CODES_RCUE_VALIDES_DE_DROIT = {ADM, CMP} CODES_RCUE_VALIDES = CODES_RCUE_VALIDES_DE_DROIT | {ADJ, ADSUP} "Niveau RCUE validé" # Pour le BUT: CODES_ANNEE_BUT_VALIDES_DE_DROIT = {ADM, PASD} # PASD pour enregistrement auto CODES_ANNEE_BUT_VALIDES = {ADM, ADSUP} CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL} BUT_BARRE_UE8 = 8.0 - NOTES_TOLERANCE BUT_BARRE_UE = BUT_BARRE_RCUE = 10.0 - NOTES_TOLERANCE BUT_RCUE_SUFFISANT = 8.0 - NOTES_TOLERANCE BUT_CODES_PASSAGE = { ADM, ADJ, PASD, PAS1NCI, ATJ, } # les codes, du plus "défavorable" à l'étudiant au plus favorable: # (valeur par défaut 0) BUT_CODES_ORDER = { ABAN: 0, ABL: 0, DEM: 0, DEF: 0, EXCLU: 0, NAR: 0, UEBSL: 0, RAT: 5, RED: 6, AJ: 10, ATJ: 20, CMP: 50, ADC: 50, PAS1NCI: 50, PASD: 60, ADJR: 90, ADSUP: 90, ADJ: 90, ADM: 100, } def code_semestre_validant(code: str) -> bool: "Vrai si ce CODE entraine la validation du semestre" return code in CODES_SEM_VALIDES def code_semestre_attente(code: str) -> bool: "Vrai si ce CODE est un code d'attente (semestre validable plus tard par jury ou compensation)" return code in CODES_SEM_ATTENTES def code_ue_validant(code: str) -> bool: "Vrai si ce code d'UE est validant (ie attribue les ECTS)" return code in CODES_UE_VALIDES def code_rcue_validant(code: str) -> bool: "Vrai si ce code d'RCUE est validant" return code in CODES_RCUE_VALIDES def code_annee_validant(code: str) -> bool: "Vrai si code d'année BUT validant" return code in CODES_ANNEE_BUT_VALIDES DEVENIR_EXPL = { NEXT: "Passage au semestre suivant", REDOANNEE: "Redoublement année", REDOSEM: "Redoublement semestre", RA_OR_NEXT: "Passage, ou redoublement année", RA_OR_RS: "Redoublement année, ou redoublement semestre", # slt si sems decales RS_OR_NEXT: "Passage, ou redoublement semestre", NEXT_OR_NEXT2: "Passage en semestre suivant ou à celui d'après", NEXT2: "Passage au sur-suivant", REO: "Réorientation", } # Devenirs autorises dans les cursus sans semestres décalés: DEVENIRS_STD = {NEXT: 1, REDOANNEE: 1, RA_OR_NEXT: 1, REO: 1} # Devenirs autorises dans les cursus en un seul semestre, semestre_id==-1 (licences ?) DEVENIRS_MONO = {REDOANNEE: 1, REO: 1} # Devenirs supplementaires (en mode manuel) pour les cursus avec semestres decales DEVENIRS_DEC = {REDOSEM: 1, RS_OR_NEXT: 1} # Devenirs en n+2 (sautant un semestre) (si semestres décalés et s'il ne manque qu'un semestre avant le n+2) DEVENIRS_NEXT2 = {NEXT_OR_NEXT2: 1, NEXT2: 1} NO_SEMESTRE_ID = -1 # code semestre si pas de semestres # Règles gestion cursus class DUTRule(object): def __init__(self, rule_id, premise, conclusion): self.rule_id = rule_id self.premise = premise self.conclusion = conclusion # self.code, self.codes_ue, self.devenir, self.action, self.explication = conclusion def match(self, state): "True if state match rule premise" assert len(state) == len(self.premise) for i, stat in enumerate(state): prem = self.premise[i] if isinstance(prem, (list, tuple)): if not stat in prem: return False else: if prem not in (ALL, stat): return False return True # Types de cursus DEFAULT_TYPE_CURSUS = 700 # (BUT) pour le menu de creation nouvelle formation class TypeCursus: TYPE_CURSUS = None # id, utilisé par notes_formation.type_parcours NAME = None # required NB_SEM = 1 # Nombre de semestres COMPENSATION_UE = True # inutilisé BARRE_MOY = 10.0 BARRE_UE_DEFAULT = 8.0 BARRE_UE_DISPLAY_WARNING = 8.0 BARRE_UE = {} NOTES_BARRE_VALID_UE_TH = 10.0 # seuil pour valider UE NOTES_BARRE_VALID_UE = NOTES_BARRE_VALID_UE_TH - NOTES_TOLERANCE # barre sur UE ALLOW_SEM_SKIP = False # Passage: autorise-t-on les sauts de semestres ? SESSION_NAME = "semestre" SESSION_NAME_A = "du " SESSION_ABBRV = "S" # S1, S2, ... UNUSED_CODES = set() # Ensemble des codes jury non autorisés dans ce cursus UE_IS_MODULE = False # 1 seul module par UE (si plusieurs modules, etudiants censéments inscrits à un seul d'entre eux) ECTS_ONLY = False # Cursus avec progression basée uniquement sur les ECTS ALLOWED_UE_TYPES = list( UE_TYPE_NAME.keys() ) # par defaut, autorise tous les types d'UE APC_SAE = False # Approche par compétences avec ressources et SAÉs USE_REFERENTIEL_COMPETENCES = False # Lien avec ref. comp. ECTS_FONDAMENTAUX_PER_YEAR = 0.0 # pour ISCID, deprecated def check(self, formation=None): return True, "" # status, diagnostic_message def get_barre_ue(self, ue_type, tolerance=True): """Barre pour cette UE (la valeur peut dépendre du type d'UE). Si tolerance, diminue de epsilon pour éviter les effets d'arrondis. """ if tolerance: t = NOTES_TOLERANCE else: t = 0.0 return self.BARRE_UE.get(ue_type, self.BARRE_UE_DEFAULT) - t def ues_sous_barre(self, ues_status): """Filtre les ues: liste celles ayant une moyenne sous la barre ues_status est une liste de dict ayant des entrées 'moy' et 'coef_ue' """ return [ ue_status for ue_status in ues_status if ue_status["coef_ue"] and isinstance(ue_status["moy"], float) and ue_status["moy"] < self.get_barre_ue(ue_status["ue"]["type"]) ] def check_barre_ues(self, ues_status): """True si la ou les conditions sur les UE sont valides Par defaut, vrai si les moyennes d'UE sont au dessus de la barre. Le cas des LP2014 est plus compliqué. """ n = len(self.ues_sous_barre(ues_status)) if n == 0: return True, "les UEs sont au dessus des barres" else: return False, """%d UE sous la barre""" % n # Cursus définis (instances de sous-classes de TypeCursus): SCO_CURSUS: dict[int, TypeCursus] = {} # type : Cursus def register_cursus(cursus: TypeCursus): SCO_CURSUS[int(cursus.TYPE_CURSUS)] = cursus class CursusBUT(TypeCursus): """BUT Bachelor Universitaire de Technologie""" TYPE_CURSUS = 700 NAME = "BUT" NB_SEM = 6 COMPENSATION_UE = False APC_SAE = True USE_REFERENTIEL_COMPETENCES = True ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT] register_cursus(CursusBUT()) class CursusDUT(TypeCursus): """DUT selon l'arrêté d'août 2005""" TYPE_CURSUS = 100 NAME = "DUT" NB_SEM = 4 COMPENSATION_UE = True ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT] register_cursus(CursusDUT()) class CursusDUT4(CursusDUT): """DUT (en 4 semestres sans compensations)""" TYPE_CURSUS = 110 NAME = "DUT4" COMPENSATION_UE = False register_cursus(CursusDUT4()) class CursusDUTMono(TypeCursus): """DUT en un an (FC, Années spéciales)""" TYPE_CURSUS = 120 NAME = "DUT" NB_SEM = 1 COMPENSATION_UE = False UNUSED_CODES = set((ADC, ATT, ATB)) register_cursus(CursusDUTMono()) class CursusDUT2(CursusDUT): """DUT en deux semestres (par ex.: années spéciales semestrialisées)""" TYPE_CURSUS = CodesCursus.DUT2 NAME = "DUT2" NB_SEM = 2 register_cursus(CursusDUT2()) class CursusLP(TypeCursus): """Licence Pro (en un "semestre") (pour anciennes LP. Après 2014, préférer CursusLP2014) """ TYPE_CURSUS = CodesCursus.LP NAME = "LP" NB_SEM = 1 COMPENSATION_UE = False ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT, UE_STAGE_LP] BARRE_UE_DEFAULT = 0.0 # pas de barre sur les UE "normales" BARRE_UE = {UE_STAGE_LP: 10.0} # pas de codes ATT en LP UNUSED_CODES = set((ADC, ATT, ATB)) register_cursus(CursusLP()) class CursusLP2sem(CursusLP): """Licence Pro (en deux "semestres")""" TYPE_CURSUS = CodesCursus.LP2sem NAME = "LP2sem" NB_SEM = 2 COMPENSATION_UE = True UNUSED_CODES = set((ADC,)) # autorise les codes ATT et ATB, mais pas ADC. register_cursus(CursusLP2sem()) class CursusLP2semEvry(CursusLP): """Licence Pro (en deux "semestres", U. Evry)""" TYPE_CURSUS = CodesCursus.LP2semEvry NAME = "LP2semEvry" NB_SEM = 2 COMPENSATION_UE = True register_cursus(CursusLP2semEvry()) class CursusLP2014(TypeCursus): """Licence Pro (en un "semestre"), selon arrêté du 22/01/2014""" # Note: texte de référence # https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000000397481 # Article 7: Le stage et le projet tutoré constituent chacun une unité d'enseignement. # Article 10: # La licence professionnelle est décernée aux étudiants qui ont obtenu à la fois une moyenne # générale égale ou supérieure à 10 sur 20 à l'ensemble des unités d'enseignement, y compris le # projet tutoré et le stage, et une moyenne égale ou supérieure à 10 sur 20 à l'ensemble constitué # du projet tutoré et du stage. # Actuellement, les points suivants de l'article 7 ("Les unités d'enseignement sont affectées par # l'établissement d'un coefficient qui peut varier dans un rapport de 1 à 3. ", etc ne sont _pas_ # vérifiés par ScoDoc) TYPE_CURSUS = CodesCursus.LP2014 NAME = "LP2014" NB_SEM = 1 ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT, UE_STAGE_LP] BARRE_UE_DEFAULT = 0.0 # pas de barre sur les UE "normales" # pas de codes ATT en LP UNUSED_CODES = set((ADC, ATT, ATB)) # Specifique aux LP BARRE_MOY_UE_STAGE_PROJET = 10.0 def check_barre_ues(self, ues_status): """True si la ou les conditions sur les UE sont valides Article 10: "une moyenne égale ou supérieure à 10 sur 20 à l'ensemble constitué du projet tutoré et du stage." """ # Les UE de type "projet ou stage" ayant des notes mc_stages_proj = [ (ue_status["moy"], ue_status["coef_ue"]) for ue_status in ues_status if ue_status["ue"]["type"] == UE_STAGE_LP and np.issubdtype(type(ue_status["moy"]), np.floating) ] # Moyenne des moyennes: sum_coef = sum(x[1] for x in mc_stages_proj) if sum_coef > 0.0: moy = sum([x[0] * x[1] for x in mc_stages_proj]) / sum_coef ok = moy > (self.BARRE_MOY_UE_STAGE_PROJET - NOTES_TOLERANCE) if ok: return True, "moyenne des UE de stages et projets au dessus de 10" else: return ( False, "moyenne des UE de stages et projets inférieure à 10", ) else: return True, "" # pas de coef, condition ok register_cursus(CursusLP2014()) class CursusLP2sem2014(CursusLP): """Licence Pro (en deux "semestres", selon arrêté du 22/01/2014)""" TYPE_CURSUS = CodesCursus.LP2sem2014 NAME = "LP2014_2sem" NB_SEM = 2 register_cursus(CursusLP2sem2014()) # Masters: M2 en deux semestres class CursusM2(TypeCursus): """Master 2 (en deux "semestres")""" TYPE_CURSUS = CodesCursus.M2 NAME = "M2sem" NB_SEM = 2 COMPENSATION_UE = True UNUSED_CODES = set((ATT, ATB)) register_cursus(CursusM2()) class CursusM2noncomp(CursusM2): """Master 2 (en deux "semestres") sans compensation""" TYPE_CURSUS = CodesCursus.M2noncomp NAME = "M2noncomp" COMPENSATION_UE = False UNUSED_CODES = set((ADC, ATT, ATB)) register_cursus(CursusM2noncomp()) class CursusMono(TypeCursus): """Formation générique en une session""" TYPE_CURSUS = CodesCursus.Mono NAME = "Mono" NB_SEM = 1 COMPENSATION_UE = False UNUSED_CODES = set((ADC, ATT, ATB)) register_cursus(CursusMono()) class CursusLegacy(TypeCursus): """DUT (ancien ScoDoc, ne plus utiliser)""" TYPE_CURSUS = CodesCursus.Legacy NAME = "DUT" NB_SEM = 4 COMPENSATION_UE = None # backward compat: defini dans formsemestre ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT] register_cursus(CursusLegacy()) class CursusISCID(TypeCursus): """Superclasse pour les cursus de l'ISCID""" # SESSION_NAME = "année" # SESSION_NAME_A = "de l'" # SESSION_ABBRV = 'A' # A1, A2, ... COMPENSATION_UE = False UNUSED_CODES = set((ADC, ATT, ATB, ATJ)) UE_IS_MODULE = True # pas de matieres et modules ECTS_ONLY = True # jury basés sur les ECTS (pas moyenne generales, pas de barres, pas de compensations) ALLOWED_UE_TYPES = [UE_STANDARD, UE_ELECTIVE, UE_PROFESSIONNELLE] NOTES_BARRE_VALID_MODULE_TH = 10.0 NOTES_BARRE_VALID_MODULE = ( NOTES_BARRE_VALID_MODULE_TH - NOTES_TOLERANCE ) # barre sur module ECTS_BARRE_VALID_YEAR = 60 ECTS_FONDAMENTAUX_PER_YEAR = 42 # mini pour valider l'annee ECTS_PROF_DIPL = 0 # crédits professionnels requis pour obtenir le diplôme class CursusBachelorISCID6(CursusISCID): """ISCID: Bachelor en 3 ans (6 sem.)""" NAME = "CursusBachelorISCID6" TYPE_CURSUS = CodesCursus.ISCID6 NAME = "" NB_SEM = 6 ECTS_PROF_DIPL = 8 # crédits professionnels requis pour obtenir le diplôme register_cursus(CursusBachelorISCID6()) class CursusMasterISCID4(CursusISCID): "ISCID: Master en 2 ans (4 sem.)" TYPE_CURSUS = CodesCursus.ISCID4 NAME = "CursusMasterISCID4" NB_SEM = 4 ECTS_PROF_DIPL = 15 # crédits professionnels requis pour obtenir le diplôme register_cursus(CursusMasterISCID4()) class CursusILEPS(TypeCursus): """Superclasse pour les cursus de l'ILEPS""" # SESSION_NAME = "année" # SESSION_NAME_A = "de l'" # SESSION_ABBRV = 'A' # A1, A2, ... COMPENSATION_UE = False UNUSED_CODES = set((ADC, ATT, ATB, ATJ)) ALLOWED_UE_TYPES = [UE_STANDARD, UE_OPTIONNELLE, UE_SPORT] # Barre moy gen. pour validation semestre: BARRE_MOY = 10.0 # Barre pour UE ILEPS: 8/20 pour UE standards ("fondamentales") # et pas de barre (-1.) pour UE élective. BARRE_UE = {UE_STANDARD: 8.0, UE_OPTIONNELLE: 0.0} BARRE_UE_DEFAULT = 0.0 # pas de barre sur les autres UE class CursusLicenceILEPS6(CursusILEPS): """ILEPS: Licence 6 semestres""" TYPE_CURSUS = 1010 NAME = "LicenceILEPS6" NB_SEM = 6 register_cursus(CursusLicenceILEPS6()) class CursusUCAC(TypeCursus): """Règles de validation UCAC""" SESSION_NAME = "année" SESSION_NAME_A = "de l'" COMPENSATION_UE = False BARRE_MOY = 12.0 NOTES_BARRE_VALID_UE_TH = 12.0 # seuil pour valider UE NOTES_BARRE_VALID_UE = NOTES_BARRE_VALID_UE_TH - NOTES_TOLERANCE # barre sur UE BARRE_UE_DEFAULT = ( NOTES_BARRE_VALID_UE_TH # il faut valider tt les UE pour valider l'année ) class CursusLicenceUCAC3(CursusUCAC): """UCAC: Licence en 3 sessions d'un an""" TYPE_CURSUS = CodesCursus.LicenceUCAC3 NAME = "Licence UCAC en 3 sessions d'un an" NB_SEM = 3 register_cursus(CursusLicenceUCAC3()) class CursusMasterUCAC2(CursusUCAC): """UCAC: Master en 2 sessions d'un an""" TYPE_CURSUS = CodesCursus.MasterUCAC2 NAME = "Master UCAC en 2 sessions d'un an" NB_SEM = 2 register_cursus(CursusMasterUCAC2()) class CursusMonoUCAC(CursusUCAC): """UCAC: Formation en 1 session de durée variable""" TYPE_CURSUS = CodesCursus.MonoUCAC NAME = "Formation UCAC en 1 session de durée variable" NB_SEM = 1 UNUSED_CODES = set((ADC, ATT, ATB)) register_cursus(CursusMonoUCAC()) class Cursus6Sem(TypeCursus): """Cursus générique en 6 semestres""" TYPE_CURSUS = CodesCursus.GEN_6_SEM NAME = "Formation en 6 semestres" NB_SEM = 6 COMPENSATION_UE = True register_cursus(Cursus6Sem()) # # En cours d'implémentation: # class CursusLicenceLMD(TypeCursus): # """Licence standard en 6 semestres dans le LMD""" # TYPE_CURSUS = 401 # NAME = "Licence LMD" # NB_SEM = 6 # COMPENSATION_UE = True # register_cursus(CursusLicenceLMD()) class CursusMasterLMD(TypeCursus): """Master générique en 4 semestres dans le LMD""" TYPE_CURSUS = CodesCursus.MasterLMD NAME = "Master LMD" NB_SEM = 4 COMPENSATION_UE = True # variabale inutilisée UNUSED_CODES = set((ADC, ATT, ATB)) register_cursus(CursusMasterLMD()) class CursusMasterIG(CursusMasterLMD): """Master de l'Institut Galilée (U. Paris 13) en 4 semestres (LMD)""" TYPE_CURSUS = CodesCursus.MasterIG NAME = "Master IG P13" BARRE_MOY = 10.0 NOTES_BARRE_VALID_UE_TH = 10.0 # seuil pour valider UE NOTES_BARRE_VALID_UE = NOTES_BARRE_VALID_UE_TH - NOTES_TOLERANCE # barre sur UE BARRE_UE_DEFAULT = 7.0 # Les UE normales avec moins de 7/20 sont éliminatoires # Specifique à l'UE de stage BARRE_MOY_UE_STAGE = 10.0 ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT, UE_STAGE_10] def check_barre_ues(self, ues_status): # inspire de la fonction de CursusLP2014 """True si la ou les conditions sur les UE sont valides moyenne d'UE > 7, ou > 10 si UE de stage """ # Il y a-t-il une UE standard sous la barre ? ue_sb = [ ue_status for ue_status in ues_status if ue_status["ue"]["type"] == UE_STANDARD and ue_status["coef_ue"] > 0 and type(ue_status["moy"]) == float and ue_status["moy"] < self.get_barre_ue(ue_status["ue"]["type"]) ] if len(ue_sb): return ( False, "%d UE sous la barre (%s/20)" % (len(ue_sb), self.BARRE_UE_DEFAULT), ) # Les UE de type "stage" ayant des notes mc_stages = [ (ue_status["moy"], ue_status["coef_ue"]) for ue_status in ues_status if ue_status["ue"]["type"] == UE_STAGE_10 and type(ue_status["moy"]) == float ] # Moyenne des moyennes: sum_coef = sum(x[1] for x in mc_stages) if sum_coef > 0.0: moy = sum([x[0] * x[1] for x in mc_stages]) / sum_coef ok = moy > (self.BARRE_MOY_UE_STAGE - NOTES_TOLERANCE) if ok: return True, "moyenne des UE de stages au dessus de 10" else: return False, "moyenne des UE de stages inférieure à 10" else: return True, "" # pas de coef, condition ok register_cursus(CursusMasterIG()) # Ajouter ici vos cursus, le TYPE_CURSUS devant être unique au monde # (avisez sur la liste de diffusion) # ... # ------------------------- _tp = list(SCO_CURSUS.items()) _tp.sort(key=lambda x: x[1].__doc__) # sort by intitulé FORMATION_CURSUS_DESCRS = [p[1].__doc__ for p in _tp] # intitulés (eg pour menu) FORMATION_CURSUS_TYPES = [p[0] for p in _tp] # codes numeriques (TYPE_CURSUS) def get_cursus_from_code(code_cursus: int) -> TypeCursus: "renvoie le cursus de code indiqué" cursus = SCO_CURSUS.get(code_cursus) if cursus is None: log(f"Warning: invalid code_cursus: {code_cursus}") # default to legacy cursus = SCO_CURSUS.get(0) return cursus