ScoDoc/app/but/jury_but.py

246 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Jury BUT: logique de gestion
"""
from operator import attrgetter
from app.comp.res_but import ResultatsSemestreBUT
from app.comp import res_sem
from app.models import but_validations
from app.models.but_refcomp import (
ApcAnneeParcours,
ApcCompetence,
ApcNiveau,
ApcParcoursNiveauCompetence,
)
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
from app.models.etudiants import Identite
from app.models.formations import Formation
from app.models.formsemestre import FormSemestre
from app.models.ues import UniteEns
from app.scodoc import sco_codes_parcours as codes
from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoException
class RegroupementCoherentUE:
def __init__(
self,
etud: Identite,
formsemestre_1: FormSemestre,
ue_1: UniteEns,
formsemestre_2: FormSemestre,
ue_2: UniteEns,
):
self.formsemestre_1 = formsemestre_1
self.ue_1 = ue_1
self.formsemestre_2 = formsemestre_2
self.ue_2 = ue_2
# stocke les moyennes d'UE
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_1)
if ue_1.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_1.id]:
self.moy_ue_1 = res.etud_moy_ue[ue_1.id][etud.id]
else:
self.moy_ue_1 = None
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_2)
if ue_2.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_2.id]:
self.moy_ue_2 = res.etud_moy_ue[ue_1.id][etud.id]
else:
self.moy_ue_2 = None
# Calcul de la moyenne au RCUE
if (self.moy_ue_1 is not None) and (self.moy_ue_2 is not None):
# Moyenne RCUE non pondérée (pour le moment)
self.moy_rcue = (self.moy_ue_1 + self.moy_ue_2) / 2
else:
self.moy_rcue = None
class DecisionsProposees:
# Codes toujours proposés sauf si include_communs est faux:
codes_communs = [codes.RAT, codes.DEF, codes.ABAN, codes.DEM, codes.UEBSL]
def __init__(self, code: str = None, explanation="", include_communs=True):
if include_communs:
self.codes = self.codes_communs
else:
self.codes = []
if isinstance(code, list):
self.codes = code + self.codes_communs
elif code is not None:
self.codes = [code] + self.codes_communs
self.explanation = explanation
def __repr__(self) -> str:
return f"""<{self.__class__.__name__} codes={self.codes} explanation={self.explanation}"""
def decisions_ue_proposees(
etud: Identite, formsemestre: FormSemestre, ue: UniteEns
) -> DecisionsProposees:
"""Liste des codes de décisions que l'on peut proposer pour
cette UE de cet étudiant dans ce semestre.
si DEF ou DEM ou ABAN ou ABL sur année BUT: seulement DEF, DEM, ABAN, ABL
si moy_ue > 10, ADM
sinon si compensation dans RCUE: CMP
sinon: ADJ, AJ
et proposer toujours: RAT, DEF, ABAN, DEM, UEBSL
"""
if ue.type == codes.UE_SPORT:
return DecisionsProposees(
explanation="UE bonus, pas de décision de jury", include_communs=False
)
# 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 codes.CODES_ANNEE_ARRET
): # DEF, DEM, ABAN, ABL
return DecisionsProposees(
code=decision_annee.code,
explanation=f"l'année a le code {decision_annee.code}",
include_communs=False,
)
# Moyenne de l'UE ?
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
if not ue.id in res.etud_moy_ue:
return DecisionsProposees(explanation="UE sans résultat")
if not etud.id in res.etud_moy_ue[ue.id]:
return DecisionsProposees(explanation="Étudiant sans résultat dans cette UE")
moy_ue = res.etud_moy_ue[ue.id][etud.id]
if moy_ue > (codes.ParcoursBUT.BARRE_MOY - codes.NOTES_TOLERANCE):
return DecisionsProposees(
code=codes.ADM,
explanation=f"Moyenne >= {codes.ParcoursBUT.BARRE_MOY}/20",
)
# Compensation dans le RCUE ?
other_ue, other_formsemestre = but_validations.get_other_ue_rcue(ue, etud.id)
if other_ue is not None:
# inscrit à une autre UE du même RCUE
other_res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(
other_formsemestre
)
if (other_ue.id in other_res.etud_moy_ue) and (
etud.id in other_res.etud_moy_ue[other_ue.id]
):
other_moy_ue = other_res.etud_moy_ue[other_ue.id][etud.id]
# Moyenne RCUE: non pondérée (pour le moment)
moy_rcue = (moy_ue + other_moy_ue) / 2
if moy_rcue > codes.NOTES_BARRE_GEN_COMPENSATION: # 10-epsilon
return DecisionsProposees(
code=codes.CMP,
explanation=f"Compensée par {other_ue} (moyenne RCUE={scu.fmt_note(moy_rcue)}/20",
)
return DecisionsProposees(
code=[codes.AJ, codes.ADJ],
explanation="notes insuffisantes",
)
def decisions_rcue_proposees(
etud: Identite,
formsemestre_1: FormSemestre,
ue_1: UniteEns,
formsemestre_2: FormSemestre,
ue_2: UniteEns,
) -> DecisionsProposees:
"""Liste des codes de décisions que l'on peut proposer pour
le RCUE de cet étudiant dans ces semestres.
ADM, CMP, ADJ, AJ, RAT, DEF, ABAN
La validation des deux UE du niveau dune compétence emporte la validation de
lensemble des UE du niveau inférieur de cette même compétence.
"""
#
class BUTCursusEtud:
"""Validation du cursus d'un étudiant"""
def __init__(self, formsemestre: FormSemestre, etud: Identite):
if formsemestre.formation.referentiel_competence is None:
raise ScoException("BUTCursusEtud: pas de référentiel de compétences")
assert len(etud.formsemestre_inscriptions) > 0
self.formsemestre = formsemestre
self.etud = etud
#
# La dernière inscription en date va donner le parcours (donc les compétences à valider)
self.last_inscription = sorted(
etud.formsemestre_inscriptions, key=attrgetter("formsemestre.date_debut")
)[-1]
def est_diplomable(self) -> bool:
"""Vrai si toutes les compétences sont validables"""
return all(
self.competence_validable(competence)
for competence in self.competences_du_parcours()
)
def est_diplome(self) -> bool:
"""Vrai si BUT déjà validé"""
# vrai si la troisième année est validée
# On cherche les validations de 3ieme annee (ordre=3) avec le même référentiel
# de formation que nous.
return (
ApcValidationAnnee.query.filter_by(etudid=self.etud.id, ordre=3)
.join(FormSemestre, FormSemestre.id == ApcValidationAnnee.formsemestre_id)
.join(Formation, FormSemestre.formation_id == Formation.id)
.filter(
Formation.referentiel_competence_id
== self.formsemestre.formation.referentiel_competence_id
)
.count()
> 0
)
def competences_du_parcours(self) -> list[ApcCompetence]:
"""Construit liste des compétences du parcours, qui doivent être
validées pour obtenir le diplôme.
Le parcours est celui de la dernière inscription.
"""
parcour = self.last_inscription.parcour
query = self.formsemestre.formation.formation.query_competences_parcour(parcour)
if query is None:
return []
return query.all()
def competence_validee(self, competence: ApcCompetence) -> bool:
"""Vrai si la compétence est validée, c'est à dire que tous ses
niveaux sont validés (ApcValidationRCUE).
"""
validations = (
ApcValidationRCUE.query.filter_by(etudid=self.etud.id)
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue1_id)
.join(ApcNiveau, ApcNiveau.id == UniteEns.niveau_competence_id)
.join(ApcCompetence, ApcCompetence.id == ApcNiveau.competence_id)
)
def competence_validable(self, competence: ApcCompetence):
"""Vrai si la compétence est "validable" automatiquement, c'est à dire
que les conditions de notes sont satisfaites pour l'acquisition de
son niveau le plus élevé, qu'il ne manque que l'enregistrement de la décision.
En vertu de la règle "La validation des deux UE du niveau dune compétence
emporte la validation de l'ensemble des UE du niveau inférieur de cette
même compétence.",
il suffit de considérer le dernier niveau dans lequel l'étudiant est inscrit.
"""
pass
def ues_emportees(self, niveau: ApcNiveau) -> list[tuple[FormSemestre, UniteEns]]:
"""La liste des UE à valider si on valide ce niveau.
Ne liste que les UE qui ne sont pas déjà acquises.
Selon la règle donéne par l'arrêté BUT:
* La validation des deux UE du niveau dune compétence emporte la validation de
l'ensemble des UE du niveau inférieur de cette même compétence.
"""
pass