forked from ScoDoc/ScoDoc
WIP: jury BUT: validations annee, RCUE, UE
This commit is contained in:
parent
cd2de51bcc
commit
a37a2c08e2
@ -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,23 +481,25 @@ 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 ?
|
self.codes.insert(0, sco_codes.CMP)
|
||||||
rcues = but_validations.find_rcues(formsemestre, ue, etud)
|
self.explanation = "compensable dans le RCUE"
|
||||||
for rcue in rcues:
|
else:
|
||||||
if rcue.est_validable():
|
# Échec à valider cette UE
|
||||||
self.codes.insert(0, sco_codes.CMP)
|
self.codes = [sco_codes.AJ, sco_codes.ADJ] + self.codes
|
||||||
self.explanation = f"Compensée par {rcue.other_ue(ue)} (moyenne RCUE={scu.fmt_note(rcue.moy_rcue)}/20"
|
self.explanation = "notes insuffisantes"
|
||||||
self.rcue = rcue
|
|
||||||
return # s'arrête au 1er RCU validable
|
|
||||||
|
|
||||||
# Échec à valider cette UE
|
|
||||||
self.codes = [sco_codes.AJ, sco_codes.ADJ] + self.codes
|
|
||||||
self.explanation = "notes insuffisantes"
|
|
||||||
|
|
||||||
|
|
||||||
class BUTCursusEtud: # WIP TODO
|
class BUTCursusEtud: # WIP TODO
|
||||||
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user