BUT: Calcul des UEs à valider par parcour. WIP: tets unitaire écrit mais ne passe pas (manque assoc UE à plusieurs parcours)

This commit is contained in:
Emmanuel Viennet 2023-03-29 23:57:50 +02:00
parent 1a3ac62f24
commit 3a051672c3
6 changed files with 96 additions and 5 deletions

View File

@ -16,6 +16,7 @@ from app.comp.res_compat import NotesTableCompat
from app.comp.bonus_spo import BonusSport from app.comp.bonus_spo import BonusSport
from app.models import ScoDocSiteConfig from app.models import ScoDocSiteConfig
from app.models.moduleimpls import ModuleImpl from app.models.moduleimpls import ModuleImpl
from app.models.but_refcomp import ApcParcours, ApcNiveau
from app.models.ues import DispenseUE, UniteEns from app.models.ues import DispenseUE, UniteEns
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
@ -41,6 +42,8 @@ class ResultatsSemestreBUT(NotesTableCompat):
"""ndarray (etuds x modimpl x ue)""" """ndarray (etuds x modimpl x ue)"""
self.etuds_parcour_id = None self.etuds_parcour_id = None
"""Parcours de chaque étudiant { etudid : parcour_id }""" """Parcours de chaque étudiant { etudid : parcour_id }"""
self.ues_ids_by_parcour: dict[set[int]] = {}
"""{ parcour_id : set }, ue_id de chaque parcours"""
if not self.load_cached(): if not self.load_cached():
t0 = time.time() t0 = time.time()
@ -259,10 +262,43 @@ class ResultatsSemestreBUT(NotesTableCompat):
def etud_ues_ids(self, etudid: int) -> list[int]: def etud_ues_ids(self, etudid: int) -> list[int]:
"""Liste des id d'UE auxquelles l'étudiant est inscrit (sans bonus). """Liste des id d'UE auxquelles l'étudiant est inscrit (sans bonus).
(surchargée ici pour prendre en compte les parcours) (surchargée ici pour prendre en compte les parcours)
Ne prend pas en compte les éventuelles DispenseUE (pour le moment ?)
""" """
s = self.ues_inscr_parcours_df.loc[etudid] s = self.ues_inscr_parcours_df.loc[etudid]
return s.index[s.notna()] return s.index[s.notna()]
def etud_parcours_ues_ids(self, etudid: int) -> set[int]:
"""Ensemble les id des UEs que l'étudiant doit valider dans ce semestre compte tenu
du parcours dans lequel il est inscrit.
Se base sur le parcours dans ce semestre, et le référentiel de compétences.
Note: il n'est pas nécessairement inscrit à toutes ces UEs.
Ensemble vide si pas de référentiel.
La requête est longue, les ue_ids par parcour sont donc cachés.
"""
parcour_id = self.etuds_parcour_id[etudid]
if parcour_id in self.ues_ids_by_parcour: # cache
return self.ues_ids_by_parcour[parcour_id]
# Hors cache:
ref_comp = self.formsemestre.formation.referentiel_competence
if ref_comp is None:
return set()
parcour: ApcParcours = ApcParcours.query.get(parcour_id)
annee = (self.formsemestre.semestre_id + 1) // 2
niveaux = ApcNiveau.niveaux_annee_de_parcours(parcour, annee, ref_comp)
# Les UEs du formsemestre associées à ces niveaux:
ues_parcour = self.formsemestre.formation.query_ues_parcour(parcour)
ues_ids = set()
for niveau in niveaux:
ue = ues_parcour.filter_by(niveau_competence=niveau).first()
if ue:
ues_ids.add(ue.id)
# memoize
self.ues_ids_by_parcour[parcour_id] = ues_ids
return ues_ids
def etud_has_decision(self, etudid): def etud_has_decision(self, etudid):
"""True s'il y a une décision de jury pour cet étudiant émanant de ce formsemestre. """True s'il y a une décision de jury pour cet étudiant émanant de ce formsemestre.
prend aussi en compte les autorisations de passage. prend aussi en compte les autorisations de passage.

View File

@ -125,6 +125,13 @@ class ResultatsSemestre(ResultatsCache):
# car tous les étudiants sont inscrits à toutes les UE # car tous les étudiants sont inscrits à toutes les UE
return [ue.id for ue in self.ues if ue.type != UE_SPORT] return [ue.id for ue in self.ues if ue.type != UE_SPORT]
def etud_parcours_ues_ids(self, etudid: int) -> set[int]:
"""Ensemble des UEs que l'étudiant "doit" valider.
En formations classiques, c'est la même chose (en set) que etud_ues_ids.
Surchargée en BUT pour donner les UEs du parcours de l'étudiant.
"""
return {ue.id for ue in self.ues if ue.type != UE_SPORT}
def etud_ues(self, etudid: int) -> Generator[UniteEns]: def etud_ues(self, etudid: int) -> Generator[UniteEns]:
"""Liste des UE auxquelles l'étudiant est inscrit """Liste des UE auxquelles l'étudiant est inscrit
(sans bonus, en BUT prend en compte le parcours de l'étudiant).""" (sans bonus, en BUT prend en compte le parcours de l'étudiant)."""

View File

@ -214,7 +214,6 @@ class ApcReferentielCompetences(db.Model, XMLModel):
""" """
parcours_info = {} parcours_info = {}
for parcour in self.parcours: for parcour in self.parcours:
print(f"# Parcours {parcour.code}")
descr_parcour = {} descr_parcour = {}
parcours_info[parcour.id] = descr_parcour parcours_info[parcour.id] = descr_parcour
for annee in (1, 2, 3): for annee in (1, 2, 3):

View File

@ -676,7 +676,7 @@ class RowRecap(tb.Row):
# Les moyennes des modules (ou ressources et SAÉs) dans cette UE # Les moyennes des modules (ou ressources et SAÉs) dans cette UE
self.add_ue_modimpls_cols(ue, ue_status["is_capitalized"]) self.add_ue_modimpls_cols(ue, ue_status["is_capitalized"])
self.nb_ues_etud_parcours = len(res.etud_ues_ids(etud.id)) self.nb_ues_etud_parcours = len(res.etud_parcours_ues_ids(etud.id))
ue_valid_txt = ( ue_valid_txt = (
ue_valid_txt_html ue_valid_txt_html
) = f"{self.nb_ues_validables}/{self.nb_ues_etud_parcours}" ) = f"{self.nb_ues_validables}/{self.nb_ues_etud_parcours}"

View File

@ -79,15 +79,44 @@ Formation:
# S5 Parcours BAT + TP # S5 Parcours BAT + TP
'UE5.1': # Parcours BAT seulement 'UE5.1': # Parcours BAT seulement
annee: BUT3 annee: BUT3
parcours: BAT # + RAPEB, BEC
competence: "Solutions Bâtiment" competence: "Solutions Bâtiment"
'UE5.2': # Parcours TP seulement 'UE5.2': # Parcours TP seulement
annee: BUT3 annee: BUT3
parcours: TP # + BEC
competence: "Solutions TP" competence: "Solutions TP"
'UE5.3':
annee: BUT3
parcours: RAPEB # + BEC
competence: "Dimensionner"
'UE5.4': 'UE5.4':
annee: BUT3 annee: BUT3
parcours: BAT # + TP
competence: Organiser competence: Organiser
'UE5.5': 'UE5.5':
annee: BUT3 annee: BUT3
parcours: BAT # + TP
competence: Piloter
# S6 Parcours BAT + TP
'UE6.1': # Parcours BAT seulement
annee: BUT3
parcours: BAT # + RAPEB, BEC
competence: "Solutions Bâtiment"
'UE6.2': # Parcours TP seulement
annee: BUT3
parcours: TP # + BEC
competence: "Solutions TP"
'UE6.3':
annee: BUT3
parcours: RAPEB # + BEC
competence: "Dimensionner"
'UE6.4':
annee: BUT3
parcours: BAT # + TP
competence: Organiser
'UE6.5':
annee: BUT3
parcours: BAT # + TP
competence: Piloter competence: Piloter
modules_parcours: modules_parcours:
@ -157,7 +186,7 @@ Etudiants:
S5: S5:
parcours: BAT parcours: BAT
dispense_ues: ['UE5.2'] dispense_ues: ['UE5.2', 'UE5.3']
notes_modules: notes_modules:
"R5.01": 15 # toutes UE "R5.01": 15 # toutes UE
"SAÉ 5.BAT.01": 10 # UE5.1 "SAÉ 5.BAT.01": 10 # UE5.1
@ -188,7 +217,7 @@ Etudiants:
S5: S5:
parcours: TP parcours: TP
dispense_ues: ['UE5.1'] dispense_ues: ['UE5.1', 'UE5.3']
notes_modules: notes_modules:
"R5.01": 15 # toutes UE "R5.01": 15 # toutes UE
"SAÉ 5.BAT.01": 10 # UE5.1 "SAÉ 5.BAT.01": 10 # UE5.1

View File

@ -24,7 +24,7 @@ from tests.unit import yaml_setup, yaml_setup_but
import app import app
from app.but.jury_but_validation_auto import formsemestre_validation_auto_but from app.but.jury_but_validation_auto import formsemestre_validation_auto_but
from app.models import FormSemestre from app.models import Formation, FormSemestre
from config import TestConfig from config import TestConfig
DEPT = TestConfig.DEPT_TEST DEPT = TestConfig.DEPT_TEST
@ -124,3 +124,23 @@ def test_but_jury_GCCD_CY(test_client):
formsemestres = FormSemestre.query.order_by( formsemestres = FormSemestre.query.order_by(
FormSemestre.date_debut, FormSemestre.semestre_id FormSemestre.date_debut, FormSemestre.semestre_id
).all() ).all()
formation: Formation = formsemestres[0].formation
# Vérifie les UEs du parcours BAT
parcour_BAT = formation.referentiel_competence.parcours.filter_by(
code="BAT"
).first()
assert parcour_BAT
# check le nombre d'UE dans chaque semestre BUT:
assert [
len(formation.query_ues_parcour(parcour_BAT).filter_by(semestre_idx=i).all())
for i in range(1, 7)
] == [5, 5, 5, 5, 3, 3]
# Vérifie les UEs du parcours TP
parcour_TP = formation.referentiel_competence.parcours.filter_by(code="TP").first()
assert parcour_TP
# check le nombre d'UE dans chaque semestre BUT:
assert [
len(formation.query_ues_parcour(parcour_TP).filter_by(semestre_idx=i).all())
for i in range(1, 7)
] == [5, 5, 5, 5, 3, 3]