ScoDocMM/tests/unit/test_but_modules.py

313 lines
12 KiB
Python

"""
Test modèles évaluations avec poids BUT
et calcul moyennes modules
"""
import datetime
import numpy as np
import pandas as pd
from tests.unit import setup
from app import db
from app.models import Evaluation, EvaluationUEPoids, Module, ModuleImpl, UniteEns
from app.comp import moy_mod
from app.comp import moy_ue
from app.scodoc import sco_saisie_notes
from app.scodoc.sco_utils import (
NOTES_ATTENTE,
NOTES_NEUTRALISE,
NOTES_SUPPRESS,
NOTES_PRECISION,
)
"""
mapp.set_sco_dept("RT")
from app.auth.models import get_super_admin
admin_user = get_super_admin()
ctx.push()
login_user(admin_user)
"""
def same_note(x, y):
return abs(x - y) < NOTES_PRECISION
def test_evaluation_poids(test_client):
"""Association de poids vers les UE"""
G, formation_id, (ue1_id, ue2_id, ue3_id), module_ids = setup.build_formation_test()
formsemestre_id = G.create_formsemestre(
formation_id=formation_id,
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
moduleimpl_id = G.create_moduleimpl(
module_id=module_ids[0],
formsemestre_id=formsemestre_id,
)
_e1 = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
date_debut=datetime.datetime(2021, 1, 1),
description="evaluation 1",
coefficient=0,
)
evaluation_id = _e1["evaluation_id"] # evaluation_id=25246
# ue1_id=1684
# formation_id=199
#
e1 = db.session.get(Evaluation, evaluation_id)
ue1 = db.session.get(UniteEns, ue1_id)
assert e1.ue_poids == []
p1 = 3.14
e1.set_ue_poids(ue1, p1)
db.session.commit()
assert e1.get_ue_poids_dict()[ue1_id] == p1
ues = UniteEns.query.filter_by(formation_id=formation_id, semestre_idx=2).all()
poids = [1.0, 2.0, 3.0]
for ue, p in zip(ues, poids):
e1.set_ue_poids(ue, p)
assert len(e1.ue_poids) == len(ues)
assert e1.get_ue_poids_dict()[ues[1].id] == poids[1]
e1.set_ue_poids(ue1, p1)
db.session.commit()
poids2 = [10, 20]
e1.update_ue_poids_dict({ue.id: p for (ue, p) in zip(ues[:-1], poids2)})
assert e1.get_ue_poids_dict()[ues[0].id] == poids2[0]
assert e1.get_ue_poids_dict()[ues[1].id] == poids2[1]
assert e1.get_ue_poids_dict()[ues[2].id] == poids[2]
# Delete UE
db.session.delete(ues[2])
db.session.commit()
# Delete eval
db.session.delete(e1)
db.session.commit()
assert len(EvaluationUEPoids.query.all()) == 0
def test_modules_coefs(test_client):
"""Coefs vers les UE (BUT)"""
G, formation_id, (ue1_id, ue2_id, ue3_id), module_ids = setup.build_formation_test()
ue1 = db.session.get(UniteEns, ue1_id)
ue2 = db.session.get(UniteEns, ue2_id)
mod = db.session.get(Module, module_ids[0])
coef = 2.5
mod.set_ue_coef(ue1, coef)
db.session.commit()
assert mod.ue_coefs[0].coef == coef
mod.set_ue_coef(ue2, 2 * coef)
db.session.commit()
assert set(mod.get_ue_coef_dict().values()) == {coef, 2 * coef}
assert set(mod.get_ue_coef_dict().keys()) == {ue1_id, ue2_id}
mod.delete_ue_coef(ue1)
db.session.commit()
assert len(mod.ue_coefs) == 1
# Gestion des coefs nuls:
mod.set_ue_coef(ue2, 0.0)
db.session.commit()
assert len(mod.ue_coefs) == 0
def test_module_conformity(test_client):
"""Vérification coefficients module<->UE vs poids des évaluations"""
(
_,
formation_id,
_,
evaluation_ids,
ue1,
ue2,
ue3,
) = setup.build_modules_with_evaluations()
semestre_idx = 2
nb_ues = 3 # 3 UEs dans ce test
nb_mods = 1 # 1 seul module
nb_evals = 1 # 1 seule evaluation pour l'instant
p1, p2, p3 = 1.0, 2.0, 0.0 # poids de l'éval vers les UE 1, 2 et 3
evaluation = db.session.get(Evaluation, evaluation_ids[0])
evaluation.set_ue_poids_dict({ue1.id: p1, ue2.id: p2, ue3.id: p3})
assert evaluation.get_ue_poids_dict() == {ue1.id: p1, ue2.id: p2, ue3.id: p3}
# On n'est pas conforme car p3 est nul alors que c3 est non nul
modules_coefficients, _ues, _modules = moy_ue.df_load_module_coefs(
formation_id, semestre_idx
)
assert isinstance(modules_coefficients, pd.DataFrame)
assert modules_coefficients.shape == (nb_ues, nb_mods)
evals_poids, ues = moy_mod.load_evaluations_poids(evaluation.moduleimpl_id)
assert isinstance(evals_poids, pd.DataFrame)
assert len(ues) == nb_ues
assert all(evals_poids.dtypes == np.float64)
assert evals_poids.shape == (nb_evals, nb_ues)
assert not moy_mod.moduleimpl_is_conforme(
evaluation.moduleimpl, evals_poids, modules_coefficients
)
# En ScoDoc 9.2 test ne peut plus exister car compute_module_moy
# est maintenant incorporé dans la classe ModuleImplResultsAPC
# def test_module_moy_elem(test_client):
# """Vérification calcul moyenne d'un module
# (notes entrées dans un DataFrame sans passer par ScoDoc)
# """
# # Création de deux évaluations:
# e1 = Evaluation(note_max=20.0, coefficient=1.0)
# e2 = Evaluation(note_max=20.0, coefficient=1.0)
# db.session.add(e1)
# db.session.add(e2)
# db.session.flush()
# # Repris du notebook CalculNotesBUT.ipynb
# data = [ # Les notes de chaque étudiant dans les 2 evals:
# {
# e1.id: 11.0,
# e2.id: 16.0,
# },
# {
# e1.id: None, # une absence
# e2.id: 17.0,
# },
# {
# e1.id: 13.0,
# e2.id: NOTES_NEUTRALISE, # une abs EXC
# },
# {
# e1.id: 14.0,
# e2.id: 19.0,
# },
# {
# e1.id: NOTES_ATTENTE, # une ATT (traitée comme EXC)
# e2.id: None, # et une ABS
# },
# ]
# evals_notes_df = pd.DataFrame(
# data, index=["etud1", "etud2", "etud3", "etud4", "etud5"]
# )
# # Poids des évaluations (1 ligne / évaluation)
# data = [
# {"UE1": 1, "UE2": 0, "UE3": 0},
# {"UE1": 2, "UE2": 5, "UE3": 0},
# ]
# evals_poids_df = pd.DataFrame(data, index=[e1.id, e2.id], dtype=float)
# evaluations = [e1, e2]
# etuds_moy_module_df = moy_mod.compute_module_moy(
# evals_notes_df.fillna(0.0), evals_poids_df, evaluations, [True, True]
# )
# NAN = 666.0 # pour pouvoir comparer NaN et NaN (car NaN != NaN)
# r = etuds_moy_module_df.fillna(NAN)
# assert tuple(r.loc["etud1"]) == (14 + 1 / 3, 16.0, NAN)
# assert tuple(r.loc["etud2"]) == (11 + 1 / 3, 17.0, NAN)
# assert tuple(r.loc["etud3"]) == (13, NAN, NAN)
# assert tuple(r.loc["etud4"]) == (17 + 1 / 3, 19, NAN)
# assert tuple(r.loc["etud5"]) == (0.0, 0.0, NAN)
# # note: les notes UE3 sont toutes NAN car les poids vers l'UE3 sont nuls
def test_module_moy(test_client):
"""Test calcul moyenne module avec saisie des notes via ScoDoc"""
coef_e1, coef_e2 = 7.0, 11.0 # coefficients des évaluations
(
G,
formation_id,
formsemestre_id,
evaluation1_ids,
ue1,
ue2,
ue3,
) = setup.build_modules_with_evaluations()
etud = G.create_etud(nom="test")
G.inscrit_etudiant(formsemestre_id, etud)
etudid = etud["etudid"]
evaluation1: Evaluation = db.session.get(Evaluation, evaluation1_ids[0])
formsemestre = evaluation1.moduleimpl.formsemestre
# Crée une deuxième évaluation dans le même moduleimpl:
evaluation2_id = G.create_evaluation(
moduleimpl_id=evaluation1.moduleimpl_id,
date_debut=datetime.datetime(2021, 1, 2),
description="evaluation 2",
coefficient=coef_e2,
)["evaluation_id"]
evaluation2 = db.session.get(Evaluation, evaluation2_id)
# Coefficients de l'eval 1
evaluation1.coefficient = coef_e1
# Poids des évaluations:
e1p1, e1p2, e1p3 = 1.0, 2.0, 0.0 # poids de l'éval 1 vers les UE 1, 2 et 3
e2p1, e2p2, e2p3 = 0.0, 1.0, 0.0 # poids de l'éval 2 vers les UE
evaluation1.set_ue_poids_dict({ue1.id: e1p1, ue2.id: e1p2, ue3.id: e1p3})
evaluation2.set_ue_poids_dict({ue1.id: e2p1, ue2.id: e2p2, ue3.id: e2p3})
# Vérifications
moduleimpl_id = evaluation1.moduleimpl_id
nb_evals = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).count()
assert nb_evals == 2
nb_ues = 3
modimpl = db.session.get(ModuleImpl, moduleimpl_id)
# --- Change les notes et recalcule les moyennes du module
# (rappel: on a deux évaluations: evaluation1, evaluation2, et un seul étudiant)
def change_notes(n1, n2):
# Saisie d'une note dans chaque éval
_ = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, n1)])
_ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)])
# Calcul de la moyenne du module
evals_poids = modimpl.get_evaluations_poids()
assert evals_poids.shape == (nb_evals, nb_ues)
etudids, etudids_actifs = formsemestre.etudids_actifs()
mod_results = moy_mod.ModuleImplResultsAPC(modimpl, etudids, etudids_actifs)
evals_notes = mod_results.evals_notes
assert evals_notes[evaluation1.id].dtype == np.float64
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
return etuds_moy_module
# --- Notes ordinaires:
note1, note2 = 11.0, 12.0
sum_copo1 = e1p1 * coef_e1 + e2p1 * coef_e2 # coefs vers UE1
sum_copo2 = e1p2 * coef_e1 + e2p2 * coef_e2 #
etuds_moy_module = change_notes(note1, note2)
moy_ue1 = etuds_moy_module[ue1.id][etudid]
assert moy_ue1 == ((note1 * e1p1 * coef_e1) + (note2 * e2p1 * coef_e2)) / sum_copo1
moy_ue2 = etuds_moy_module[ue2.id][etudid]
assert moy_ue2 == ((note1 * e1p2 * coef_e1) + (note2 * e2p2 * coef_e2)) / sum_copo2
moy_ue3 = etuds_moy_module[ue3.id][etudid]
assert np.isnan(moy_ue3) # car les poids vers UE3 sont nuls
# --- Une note ABS (comptée comme zéro)
etuds_moy_module = change_notes(None, note2)
assert etuds_moy_module[ue1.id][etudid] == (note2 * e2p1 * coef_e2) / sum_copo1
assert etuds_moy_module[ue2.id][etudid] == (note2 * e2p2 * coef_e2) / sum_copo2
assert np.isnan(etuds_moy_module[ue3.id][etudid])
# --- Deux notes ABS
etuds_moy_module = change_notes(None, None)
assert etuds_moy_module[ue1.id][etudid] == 0.0
assert etuds_moy_module[ue2.id][etudid] == 0.0
assert np.isnan(etuds_moy_module[ue3.id][etudid])
# --- Note EXC
etuds_moy_module = change_notes(NOTES_ATTENTE, note2)
assert np.isnan(
etuds_moy_module[ue1.id][etudid]
) # car l'eval 2 ne touche que l'UE2
assert etuds_moy_module[ue2.id][etudid] == note2
assert np.isnan(etuds_moy_module[ue3.id][etudid])
# --- Toutes notes ATT (ATT se traite comme EXC)
etuds_moy_module = change_notes(NOTES_NEUTRALISE, NOTES_NEUTRALISE)
assert np.isnan(etuds_moy_module[ue1.id][etudid])
assert np.isnan(etuds_moy_module[ue2.id][etudid])
assert np.isnan(etuds_moy_module[ue3.id][etudid])
# --- Barème sur 37
evaluation2.note_max = 37.0
note1, note2 = 11.0, 12.0
note_2_37 = note2 / 20 * 37
etuds_moy_module = change_notes(note1, note_2_37)
moy_ue1 = etuds_moy_module[ue1.id][etudid]
assert moy_ue1 == ((note1 * e1p1 * coef_e1) + (note2 * e2p1 * coef_e2)) / sum_copo1
moy_ue2 = etuds_moy_module[ue2.id][etudid]
assert moy_ue2 == ((note1 * e1p2 * coef_e1) + (note2 * e2p2 * coef_e2)) / sum_copo2
moy_ue3 = etuds_moy_module[ue3.id][etudid]
assert np.isnan(moy_ue3) # car les poids vers UE3 sont nuls
# --- Note manquante à l'éval. 1
note_2_37 = note2 / 20 * 37
etuds_moy_module = change_notes(NOTES_SUPPRESS, note_2_37)
assert same_note(etuds_moy_module[ue2.id][etudid], note2)
# --- Prise en compte immédiate:
evaluation1.publish_incomplete = True
etuds_moy_module = change_notes(NOTES_SUPPRESS, note_2_37)
assert same_note(etuds_moy_module[ue2.id][etudid], note2)