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 committed by iziram
parent cf778eba85
commit 4f9638582a
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.models import ScoDocSiteConfig
from app.models.moduleimpls import ModuleImpl
from app.models.but_refcomp import ApcParcours, ApcNiveau
from app.models.ues import DispenseUE, UniteEns
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
from app.scodoc import sco_preferences
@ -41,6 +42,8 @@ class ResultatsSemestreBUT(NotesTableCompat):
"""ndarray (etuds x modimpl x ue)"""
self.etuds_parcour_id = None
"""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():
t0 = time.time()
@ -259,10 +262,43 @@ class ResultatsSemestreBUT(NotesTableCompat):
def etud_ues_ids(self, etudid: int) -> list[int]:
"""Liste des id d'UE auxquelles l'étudiant est inscrit (sans bonus).
(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]
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):
"""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.

View File

@ -125,6 +125,13 @@ class ResultatsSemestre(ResultatsCache):
# car tous les étudiants sont inscrits à toutes les UE
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]:
"""Liste des UE auxquelles l'étudiant est inscrit
(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 = {}
for parcour in self.parcours:
print(f"# Parcours {parcour.code}")
descr_parcour = {}
parcours_info[parcour.id] = descr_parcour
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
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_html
) = f"{self.nb_ues_validables}/{self.nb_ues_etud_parcours}"

View File

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

View File

@ -24,7 +24,7 @@ from tests.unit import yaml_setup, yaml_setup_but
import app
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
DEPT = TestConfig.DEPT_TEST
@ -124,3 +124,23 @@ def test_but_jury_GCCD_CY(test_client):
formsemestres = FormSemestre.query.order_by(
FormSemestre.date_debut, FormSemestre.semestre_id
).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]