diff --git a/app/comp/moy_ue.py b/app/comp/moy_ue.py index 27a7829d..e1cc4a16 100644 --- a/app/comp/moy_ue.py +++ b/app/comp/moy_ue.py @@ -136,8 +136,8 @@ def compute_ue_moys( etuds: list, modimpls: list, ues: list, - module_inscr_df: pd.DataFrame, - module_coefs_df: pd.DataFrame, + modimpl_inscr_df: pd.DataFrame, + modimpl_coefs_df: pd.DataFrame, ) -> pd.DataFrame: """Calcul de la moyenne d'UE La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR @@ -160,20 +160,33 @@ def compute_ue_moys( assert len(etuds) == nb_etuds assert len(modimpls) == nb_modules assert len(ues) == nb_ues - assert module_inscr_df.shape[0] == nb_etuds - assert module_inscr_df.shape[1] == nb_modules - assert module_coefs_df.shape[0] == nb_ues - assert module_coefs_df.shape[1] == nb_modules - module_inscr = module_inscr_df.values - modules_coefs = module_coefs_df.values - # - # version non vectorisée sur les etuds: - etud_moy_ue = np.zeros((nb_etuds, nb_ues)) - for i in range(nb_etuds): - coefs = module_inscr[i] * modules_coefs - etud_moy_ue[i] = (sem_cube[i].transpose() * coefs).sum(axis=1) / coefs.sum( - axis=1 - ) - return pd.DataFrame( - etud_moy_ue, index=module_inscr_df.index, columns=module_coefs_df.index + assert modimpl_inscr_df.shape[0] == nb_etuds + assert modimpl_inscr_df.shape[1] == nb_modules + assert modimpl_coefs_df.shape[0] == nb_ues + assert modimpl_coefs_df.shape[1] == nb_modules + modimpl_inscr = modimpl_inscr_df.values + modimpl_coefs = modimpl_coefs_df.values + # Duplique les inscriptions sur les UEs: + modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues, axis=2) + + # Enlève les NaN du numérateur: + # si on veut prendre en compte les module avec notes neutralisées ? + # sem_cube_no_nan = np.nan_to_num(sem_cube, nan=0.0) + + # Ne prend pas en compte les notes des étudiants non inscrits au module: + # Annule les notes: + sem_cube_inscrits = np.where(modimpl_inscr_stacked, sem_cube, 0.0) + # Annule les coefs des modules où l'étudiant n'est pas inscrit: + modimpl_coefs_etuds = np.where( + modimpl_inscr_stacked, np.stack([modimpl_coefs.T] * nb_etuds), 0.0 + ) + + # + # Version vectorisée + # + etud_moy_ue = np.sum(modimpl_coefs_etuds * sem_cube_inscrits, axis=1) / np.sum( + modimpl_coefs_etuds, axis=1 + ) + return pd.DataFrame( + etud_moy_ue, index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index ) diff --git a/tests/unit/test_but_ues.py b/tests/unit/test_but_ues.py index 330cab96..1b17ef3d 100644 --- a/tests/unit/test_but_ues.py +++ b/tests/unit/test_but_ues.py @@ -2,20 +2,21 @@ Test calcul moyennes UE """ import numpy as np +from numpy.lib.nanfunctions import _nanquantile_1d import pandas as pd from tests.unit import setup -from app.models.etudiants import Identite - from tests.unit import sco_fake_gen from app import db from app import models from app.comp import moy_mod from app.comp import moy_ue from app.comp import inscr_mod -from app.models import Evaluation, formsemestre +from app.models import FormSemestre, Evaluation, ModuleImplInscription +from app.models.etudiants import Identite from app.scodoc import sco_codes_parcours, sco_saisie_notes from app.scodoc.sco_utils import NOTES_ATTENTE, NOTES_NEUTRALISE +from app.scodoc import sco_exceptions def test_ue_moy(test_client): @@ -34,9 +35,9 @@ def test_ue_moy(test_client): ) = setup.build_modules_with_evaluations(ue_coefs=ue_coefs, nb_mods=nb_mods) assert len(evaluation_ids) == nb_mods formsemestre_id = sem["formsemestre_id"] - formsemestre = models.FormSemestre.query.get(formsemestre_id) - evaluation1 = models.Evaluation.query.get(evaluation_ids[0]) - evaluation2 = models.Evaluation.query.get(evaluation_ids[1]) + formsemestre = FormSemestre.query.get(formsemestre_id) + evaluation1 = Evaluation.query.get(evaluation_ids[0]) + evaluation2 = Evaluation.query.get(evaluation_ids[1]) etud = G.create_etud(nom="test") G.inscrit_etudiant(sem, etud) etudid = etud["etudid"] @@ -78,9 +79,46 @@ def test_ue_moy(test_client): # Cas simple: 1 eval / module, notes normales, # coefs non nuls. n1, n2 = 5.0, 13.0 # notes aux 2 evals (1 dans chaque module) - etud_moy_ue = change_notes(5.0, 13.0) + etud_moy_ue = change_notes(n1, n2) assert etud_moy_ue.shape == (1, nb_ues) # 1 étudiant assert etud_moy_ue[ue1.id][etudid] == (n1 + n2) / 2 assert etud_moy_ue[ue2.id][etudid] == (n1 + n2) / 2 assert etud_moy_ue[ue3.id][etudid] == (n1 + n2) / 2 # + # ABS à un module (note comptée comme 0) + n1, n2 = None, 13.0 # notes aux 2 evals (1 dans chaque module) + etud_moy_ue = change_notes(n1, n2) + assert etud_moy_ue[ue1.id][etudid] == n2 / 2 # car n1 est zéro + assert etud_moy_ue[ue2.id][etudid] == n2 / 2 + assert etud_moy_ue[ue3.id][etudid] == n2 / 2 + # EXC à un module + n1, n2 = 5.0, NOTES_NEUTRALISE + etud_moy_ue = change_notes(n1, n2) + # Pour le moment, une note NEUTRALISE var entrainer le non calcul + # des moyennes. + assert np.isnan(etud_moy_ue.values).all() + # Désinscrit l'étudiant du module 2: + inscr = ModuleImplInscription.query.filter_by( + moduleimpl_id=evaluation2.moduleimpl.id, etudid=etudid + ).first() + db.session.delete(inscr) + db.session.commit() + modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(formsemestre_id) + assert (modimpl_inscr_df.values == np.array([[1, 0]])).all() + n1, n2 = 5.0, NOTES_NEUTRALISE + # On ne doit pas pouvoir saisir de note sans être inscrit: + exception_raised = False + try: + etud_moy_ue = change_notes(n1, n2) + except sco_exceptions.NoteProcessError: + exception_raised = True + assert exception_raised + # Recalcule les notes: + sem_cube = moy_ue.notes_sem_load_cube(formsemestre_id) + etuds = formsemestre.etuds.all() + etud_moy_ue = moy_ue.compute_ue_moys( + sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df + ) + assert etud_moy_ue[ue1.id][etudid] == n1 + assert etud_moy_ue[ue2.id][etudid] == n1 + assert etud_moy_ue[ue3.id][etudid] == n1