WIP: jury BUT: validations annee, RCUE, UE

This commit is contained in:
Emmanuel Viennet 2022-06-21 11:21:17 +02:00
parent cd2de51bcc
commit a37a2c08e2
2 changed files with 87 additions and 46 deletions

View File

@ -117,15 +117,15 @@ class DecisionsProposees:
self.codes = [] self.codes = []
"Les codes attribuables par ce jury" "Les codes attribuables par ce jury"
if include_communs: if include_communs:
self.codes = self.codes_communs self.codes = self.codes_communs.copy()
if isinstance(code, list): if isinstance(code, list):
self.codes = code + self.codes_communs self.codes = code + self.codes
elif code is not None: elif code is not None:
self.codes = [code] + self.codes_communs self.codes = [code] + self.codes
self.code_valide: str = code_valide self.code_valide: str = code_valide
"La décision actuelle enregistrée" "La décision actuelle enregistrée"
self.explanation: str = explanation self.explanation: str = explanation
"Explication en à afficher à côté de la décision" "Explication à afficher à côté de la décision"
def __repr__(self) -> str: def __repr__(self) -> str:
return f"""<{self.__class__.__name__} valid={self.code_valide return f"""<{self.__class__.__name__} valid={self.code_valide
@ -193,48 +193,72 @@ class DecisionsProposeesAnnee(DecisionsProposees):
self.parcour = None self.parcour = None
"Le parcours considéré (celui du semestre pair, ou à défaut impair)" "Le parcours considéré (celui du semestre pair, ou à défaut impair)"
self.ues_impair, self.ues_pair = self.compute_ues_annee() # pylint: disable=all self.ues_impair, self.ues_pair = self.compute_ues_annee() # pylint: disable=all
self.decisions_ues = {
ue.id: DecisionsProposeesUE(etud, formsemestre_impair, ue)
for ue in self.ues_impair
}
"{ue_id : DecisionsProposeesUE} pour toutes les UE de l'année"
self.decisions_ues.update(
{
ue.id: DecisionsProposeesUE(etud, formsemestre_pair, ue)
for ue in self.ues_pair
}
)
assert self.parcour is not None assert self.parcour is not None
self.rcues_annee = self.compute_rcues_annee() self.rcues_annee = self.compute_rcues_annee()
"RCUEs de l'année" "RCUEs de l'année"
self.nb_competences = len( self.niveaux_competences = ApcNiveau.niveaux_annee_de_parcours(
ApcNiveau.niveaux_annee_de_parcours(self.parcour, self.annee_but).all() self.parcour, self.annee_but
) # note that .count() won't give the same res ).all() # XXX à trier, selon l'ordre des UE associées ?
"liste des niveaux de compétences associés à cette année"
self.decisions_rcue_by_niveau = self.compute_decisions_niveaux()
"les décisions rcue associées aux niveau_id"
self.nb_competences = len(self.niveaux_competences)
"le nombre de niveaux de compétences à valider cette année"
self.nb_validables = len( self.nb_validables = len(
[rcue for rcue in self.rcues_annee if rcue.est_validable()] [rcue for rcue in self.rcues_annee if rcue.est_validable()]
) )
"le nombre de comp. validables (éventuellement par compensation)"
self.nb_rcues_under_8 = len( self.nb_rcues_under_8 = len(
[rcue for rcue in self.rcues_annee if not rcue.est_suffisant()] [rcue for rcue in self.rcues_annee if not rcue.est_suffisant()]
) )
"le nb de comp. sous la barre de 8/20"
# année ADM si toutes RCUE validées (sinon PASD) # année ADM si toutes RCUE validées (sinon PASD)
admis = self.nb_validables == self.nb_competences admis = self.nb_validables == self.nb_competences
valide_moitie_rcue = self.nb_validables > self.nb_competences // 2 self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2)
# Peut passer si plus de la moitié validables et tous > 8 # Peut passer si plus de la moitié validables et tous > 8
passage_de_droit = valide_moitie_rcue and (self.nb_rcues_under_8 == 0) self.passage_de_droit = self.valide_moitie_rcue and (self.nb_rcues_under_8 == 0)
# XXX TODO ajouter condition pour passage en S5 # XXX TODO ajouter condition pour passage en S5
# Enfin calcule les codes des UE:
for dec_ue in self.decisions_ues.values():
dec_ue.compute_codes()
# Reste à attribuer ADM, ADJ, PASD, PAS1NCI, RED, NAR # Reste à attribuer ADM, ADJ, PASD, PAS1NCI, RED, NAR
expl_rcues = f"{self.nb_validables} validables sur {self.nb_competences}" expl_rcues = (
f"{self.nb_validables} niveau validable(s) sur {self.nb_competences}"
)
if admis: if admis:
self.codes = [sco_codes.ADM] + self.codes self.codes = [sco_codes.ADM] + self.codes
self.explanation = expl_rcues self.explanation = expl_rcues
elif passage_de_droit: elif self.passage_de_droit:
self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes
self.explanation = expl_rcues self.explanation = expl_rcues
elif valide_moitie_rcue: # mais au moins 1 rcue insuffisante elif self.valide_moitie_rcue: # mais au moins 1 rcue insuffisante
self.codes = [sco_codes.PAS1NCI, sco_codes.ADJ] + self.codes self.codes = [sco_codes.PAS1NCI, sco_codes.ADJ] + self.codes
self.explanation = expl_rcues + f" et {self.nb_rcues_under_8} < 8" self.explanation = expl_rcues + f" et {self.nb_rcues_under_8} < 8"
else: else:
self.codes = [sco_codes.RED, sco_codes.NAR, sco_codes.ADJ] + self.codes self.codes = [sco_codes.RED, sco_codes.NAR, sco_codes.ADJ] + self.codes
self.explanation = expl_rcues + f" et {self.nb_rcues_under_8} < 8" self.explanation = expl_rcues + f" et {self.nb_rcues_under_8} niveau < 8"
# #
def infos(self) -> str: def infos(self) -> str:
"informations, for debugging purpose" "informations, for debugging purpose"
return f"""DecisionsProposeesAnnee return f"""DecisionsProposeesAnnee
etud: {self.etud} etud: {self.etud}
formsemestre_pair: {self.formsemestre_pair}
formsemestre_impair: {self.formsemestre_impair} formsemestre_impair: {self.formsemestre_impair}
formsemestre_pair: {self.formsemestre_pair}
RCUEs: {self.rcues_annee} RCUEs: {self.rcues_annee}
nb_competences: {self.nb_competences} nb_competences: {self.nb_competences}
nb_nb_validables: {self.nb_validables} nb_nb_validables: {self.nb_validables}
@ -286,7 +310,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
# Parcour dans lequel l'étudiant est inscrit, et liste des UEs # Parcour dans lequel l'étudiant est inscrit, et liste des UEs
if res.etuds_parcour_id[etudid] is None: if res.etuds_parcour_id[etudid] is None:
# pas de parcour: prend toutes les UEs (non bonus) # pas de parcour: prend toutes les UEs (non bonus)
ues = res.etud_ues(etudid) ues = list(res.etud_ues(etudid))
else: else:
parcour = ApcParcours.query.get(res.etuds_parcour_id[etudid]) parcour = ApcParcours.query.get(res.etuds_parcour_id[etudid])
if parcour is not None: if parcour is not None:
@ -343,10 +367,34 @@ class DecisionsProposeesAnnee(DecisionsProposees):
raise ScoValueError(f"pas de RCUE pour l'UE {ue_pair.acronyme}") raise ScoValueError(f"pas de RCUE pour l'UE {ue_pair.acronyme}")
rcues_annee.append(rcue) rcues_annee.append(rcue)
if len(ues_impair_sans_rcue) > 0: if len(ues_impair_sans_rcue) > 0:
ue = ues_impair_sans_rcue.pop() ue = UniteEns.query.get(ues_impair_sans_rcue.pop())
raise ScoValueError(f"pas de RCUE pour l'UE {ue.acronyme}") raise ScoValueError(f"pas de RCUE pour l'UE {ue.acronyme}")
return rcues_annee return rcues_annee
def compute_decisions_niveaux(self) -> dict[int, "DecisionsProposeesRCUE"]:
"""Pour chaque niveau de compétence de cette année, donne le DecisionsProposeesRCUE
ou None s'il n'y en a pas (ne devrait pas arriver car
compute_rcues_annee vérifie déjà cela).
Return: { niveau_id : DecisionsProposeesRCUE }
"""
# Retrouve le RCUE associé à chaque niveau
rc_niveaux = []
for niveau in self.niveaux_competences:
rcue = None
for rc in self.rcues_annee:
if rc.ue_1.niveau_competence_id == niveau.id:
rcue = rc
break
dec_rcue = DecisionsProposeesRCUE(self, rcue)
rc_niveaux.append((dec_rcue, niveau.id))
# prévient les UE concernées :-)
self.decisions_ues[dec_rcue.rcue.ue_1.id].set_rcue(dec_rcue.rcue)
self.decisions_ues[dec_rcue.rcue.ue_2.id].set_rcue(dec_rcue.rcue)
# Ordonne par numéro d'UE
rc_niveaux.sort(key=lambda x: x[0].rcue.ue_1.numero)
decisions_rcue_by_niveau = {x[1]: x[0] for x in rc_niveaux}
return decisions_rcue_by_niveau
class DecisionsProposeesRCUE(DecisionsProposees): class DecisionsProposeesRCUE(DecisionsProposees):
"""Liste des codes de décisions que l'on peut proposer pour """Liste des codes de décisions que l'on peut proposer pour
@ -371,7 +419,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
validation = rcue.query_validations().first() validation = rcue.query_validations().first()
if validation is not None: if validation is not None:
self.code_valide = validation.code self.code_valide = validation.code
if rcue.est_compense(): if rcue.est_compensable():
self.codes.insert(0, sco_codes.CMP) self.codes.insert(0, sco_codes.CMP)
elif rcue.est_validable(): elif rcue.est_validable():
self.codes.insert(0, sco_codes.ADM) self.codes.insert(0, sco_codes.ADM)
@ -412,6 +460,7 @@ class DecisionsProposeesUE(DecisionsProposees):
super().__init__(etud=etud) super().__init__(etud=etud)
self.ue: UniteEns = ue self.ue: UniteEns = ue
self.rcue: RegroupementCoherentUE = None self.rcue: RegroupementCoherentUE = None
"Le rcu auquel est rattaché cette UE, ou None"
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non) # Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
# mais ici on a restreint au formsemestre donc une seule (prend la première) # mais ici on a restreint au formsemestre donc une seule (prend la première)
self.validation = ScolarFormSemestreValidation.query.filter_by( self.validation = ScolarFormSemestreValidation.query.filter_by(
@ -423,17 +472,7 @@ class DecisionsProposeesUE(DecisionsProposees):
self.explanation = "UE bonus, pas de décision de jury" self.explanation = "UE bonus, pas de décision de jury"
self.codes = [] # aucun code proposé self.codes = [] # aucun code proposé
return return
# Code sur année ?
decision_annee = ApcValidationAnnee.query.filter_by(
etudid=etud.id, annee_scolaire=formsemestre.annee_scolaire()
).first()
if (
decision_annee is not None
and decision_annee.code in sco_codes.CODES_ANNEE_ARRET
): # DEF, DEM, ABAN, ABL
self.explanation = f"l'année a le code {decision_annee.code}"
self.codes = [decision_annee.code] # sans les codes communs
return
# Moyenne de l'UE ? # Moyenne de l'UE ?
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre) res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
if not ue.id in res.etud_moy_ue: if not ue.id in res.etud_moy_ue:
@ -442,20 +481,22 @@ class DecisionsProposeesUE(DecisionsProposees):
if not etud.id in res.etud_moy_ue[ue.id]: if not etud.id in res.etud_moy_ue[ue.id]:
self.explanation = "Étudiant sans résultat dans cette UE" self.explanation = "Étudiant sans résultat dans cette UE"
return return
moy_ue = res.etud_moy_ue[ue.id][etud.id] self.moy_ue = res.etud_moy_ue[ue.id][etud.id]
if moy_ue > (sco_codes.ParcoursBUT.BARRE_MOY - sco_codes.NOTES_TOLERANCE):
def set_rcue(self, rcue: RegroupementCoherentUE):
"""Rattache cette UE à un RCUE. Cela peut modifier les codes
proposés (si compensation)"""
self.rcue = rcue
def compute_codes(self):
"""Calcul des .codes attribuables et de l'explanation associée"""
if self.moy_ue > (sco_codes.ParcoursBUT.BARRE_MOY - sco_codes.NOTES_TOLERANCE):
self.codes.insert(0, sco_codes.ADM) self.codes.insert(0, sco_codes.ADM)
self.explanation = (f"Moyenne >= {sco_codes.ParcoursBUT.BARRE_MOY}/20",) self.explanation = (f"Moyenne >= {sco_codes.ParcoursBUT.BARRE_MOY}/20",)
elif self.rcue and self.rcue.est_compensable():
# Compensation dans un RCUE ?
rcues = but_validations.find_rcues(formsemestre, ue, etud)
for rcue in rcues:
if rcue.est_validable():
self.codes.insert(0, sco_codes.CMP) self.codes.insert(0, sco_codes.CMP)
self.explanation = f"Compensée par {rcue.other_ue(ue)} (moyenne RCUE={scu.fmt_note(rcue.moy_rcue)}/20" self.explanation = "compensable dans le RCUE"
self.rcue = rcue else:
return # s'arrête au 1er RCU validable
# Échec à valider cette UE # Échec à valider cette UE
self.codes = [sco_codes.AJ, sco_codes.ADJ] + self.codes self.codes = [sco_codes.AJ, sco_codes.ADJ] + self.codes
self.explanation = "notes insuffisantes" self.explanation = "notes insuffisantes"

View File

@ -67,7 +67,7 @@ class ApcValidationRCUE(db.Model):
# Attention: ce n'est pas un modèle mais une classe ordinaire: # Attention: ce n'est pas un modèle mais une classe ordinaire:
class RegroupementCoherentUE: class RegroupementCoherentUE:
"""Le regroupement cohérent d'UE, dans la terminologie du BUT, est le couple d'UEs """Le regroupement cohérent d'UE, dans la terminologie du BUT, est le couple d'UEs
de la même année (BUT1,2,3) liées au même niveau de compétence. de la même année (BUT1,2,3) liées au *même niveau de compétence*.
La moyenne (10/20) au RCU déclenche la compensation des UE. La moyenne (10/20) au RCU déclenche la compensation des UE.
""" """
@ -139,7 +139,7 @@ class RegroupementCoherentUE:
etudid=self.etud.id, etudid=self.etud.id,
) )
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue2_id) .join(UniteEns, UniteEns.id == ApcValidationRCUE.ue2_id)
.join(ApcNiveau, UniteEns.niveau_id == ApcNiveau.id) .join(ApcNiveau, UniteEns.niveau_competence_id == ApcNiveau.id)
.filter(ApcNiveau.id == niveau.id) .filter(ApcNiveau.id == niveau.id)
) )
@ -157,7 +157,7 @@ class RegroupementCoherentUE:
""" """
return self.query_validations().count() > 0 return self.query_validations().count() > 0
def est_compense(self): def est_compensable(self):
"""Vrai si ce RCUE est validable par compensation """Vrai si ce RCUE est validable par compensation
c'est à dire que sa moyenne est > 10 avec une UE < 10 c'est à dire que sa moyenne est > 10 avec une UE < 10
""" """