forked from ScoDoc/ScoDoc
Support pour plusieurs évaluations de rattrapage en classique et BUT. Avance sur #811.
This commit is contained in:
parent
8f25284038
commit
89afb672af
@ -45,7 +45,6 @@ from app.models import Evaluation, EvaluationUEPoids, ModuleImpl
|
|||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
from app.scodoc.sco_exceptions import ScoBugCatcher
|
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
@ -275,20 +274,18 @@ class ModuleImplResults:
|
|||||||
* self.evaluations_completes
|
* self.evaluations_completes
|
||||||
).reshape(-1, 1)
|
).reshape(-1, 1)
|
||||||
|
|
||||||
def get_evaluations_session2_coefs(self, modimpl: ModuleImpl) -> np.array:
|
def get_evaluations_special_coefs(
|
||||||
"""Coefficients des évaluations de session 2.
|
self, modimpl: ModuleImpl, evaluation_type=Evaluation.EVALUATION_SESSION2
|
||||||
Les évals de session 2 sont réputées "complètes": elles sont toujours
|
) -> np.array:
|
||||||
|
"""Coefficients des évaluations de session 2 ou rattrapage.
|
||||||
|
Les évals de session 2 et rattrapage sont réputées "complètes": elles sont toujours
|
||||||
prises en compte mais seules les notes numériques et ABS sont utilisées.
|
prises en compte mais seules les notes numériques et ABS sont utilisées.
|
||||||
Résultat: 2d-array of floats, shape (nb_evals, 1)
|
Résultat: 2d-array of floats, shape (nb_evals, 1)
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
np.array(
|
np.array(
|
||||||
[
|
[
|
||||||
(
|
(e.coefficient if e.evaluation_type == evaluation_type else 0.0)
|
||||||
e.coefficient
|
|
||||||
if e.evaluation_type == Evaluation.EVALUATION_SESSION2
|
|
||||||
else 0.0
|
|
||||||
)
|
|
||||||
for e in modimpl.evaluations
|
for e in modimpl.evaluations
|
||||||
],
|
],
|
||||||
dtype=float,
|
dtype=float,
|
||||||
@ -321,19 +318,16 @@ class ModuleImplResults:
|
|||||||
for (etudid, x) in self.evals_notes[evaluation_id].items()
|
for (etudid, x) in self.evals_notes[evaluation_id].items()
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl) -> Evaluation | None:
|
def get_evaluations_rattrapage(self, moduleimpl: ModuleImpl) -> list[Evaluation]:
|
||||||
"""L'évaluation de rattrapage de ce module, ou None s'il n'en a pas.
|
"""Les évaluations de rattrapage de ce module.
|
||||||
Rattrapage: la moyenne du module est la meilleure note entre moyenne
|
Rattrapage: la moyenne du module est la meilleure note entre moyenne
|
||||||
des autres évals et la note eval rattrapage.
|
des autres évals et la moyenne des notes de rattrapage.
|
||||||
"""
|
"""
|
||||||
eval_list = [
|
return [
|
||||||
e
|
e
|
||||||
for e in moduleimpl.evaluations
|
for e in moduleimpl.evaluations
|
||||||
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE
|
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE
|
||||||
]
|
]
|
||||||
if eval_list:
|
|
||||||
return eval_list[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_evaluations_session2(self, moduleimpl: ModuleImpl) -> list[Evaluation]:
|
def get_evaluations_session2(self, moduleimpl: ModuleImpl) -> list[Evaluation]:
|
||||||
"""Les évaluations de deuxième session de ce module, ou None s'il n'en a pas.
|
"""Les évaluations de deuxième session de ce module, ou None s'il n'en a pas.
|
||||||
@ -421,25 +415,18 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
) / np.sum(evals_poids_etuds, axis=1)
|
) / np.sum(evals_poids_etuds, axis=1)
|
||||||
# etuds_moy_module shape: nb_etuds x nb_ues
|
# etuds_moy_module shape: nb_etuds x nb_ues
|
||||||
|
|
||||||
# Session2 : quand elle existe, remplace la note de module
|
|
||||||
evals_session2 = self.get_evaluations_session2(modimpl)
|
evals_session2 = self.get_evaluations_session2(modimpl)
|
||||||
|
evals_rat = self.get_evaluations_rattrapage(modimpl)
|
||||||
if evals_session2:
|
if evals_session2:
|
||||||
|
# Session2 : quand elle existe, remplace la note de module
|
||||||
# Calcul moyenne notes session2 et remplace (si la note session 2 existe)
|
# Calcul moyenne notes session2 et remplace (si la note session 2 existe)
|
||||||
evals_coefs_s2 = self.get_evaluations_session2_coefs(modimpl)
|
etuds_moy_module_s2 = self._compute_moy_special(
|
||||||
evals_poids_s2 = evals_poids_df.values * evals_coefs_s2
|
modimpl,
|
||||||
poids_stacked_s2 = np.stack(
|
evals_notes_stacked,
|
||||||
[evals_poids_s2] * nb_etuds
|
evals_poids_df,
|
||||||
) # nb_etuds, nb_evals, nb_ues
|
Evaluation.EVALUATION_SESSION2,
|
||||||
evals_poids_etuds_s2 = np.where(
|
|
||||||
np.stack([self.evals_notes.values] * nb_ues, axis=2)
|
|
||||||
> scu.NOTES_NEUTRALISE,
|
|
||||||
poids_stacked_s2,
|
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
etuds_moy_module_s2 = np.sum(
|
|
||||||
evals_poids_etuds_s2 * evals_notes_stacked, axis=1
|
|
||||||
) / np.sum(evals_poids_etuds_s2, axis=1)
|
|
||||||
# Vrai si toutes les UEs ont bien une note de session 2 calculée:
|
# Vrai si toutes les UEs ont bien une note de session 2 calculée:
|
||||||
etuds_use_session2 = np.all(np.isfinite(etuds_moy_module_s2), axis=1)
|
etuds_use_session2 = np.all(np.isfinite(etuds_moy_module_s2), axis=1)
|
||||||
etuds_moy_module = np.where(
|
etuds_moy_module = np.where(
|
||||||
@ -450,28 +437,21 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
self.etuds_use_session2 = pd.Series(
|
self.etuds_use_session2 = pd.Series(
|
||||||
etuds_use_session2, index=self.evals_notes.index
|
etuds_use_session2, index=self.evals_notes.index
|
||||||
)
|
)
|
||||||
else:
|
elif evals_rat:
|
||||||
# Rattrapage: remplace la note de module ssi elle est supérieure
|
etuds_moy_module_rat = self._compute_moy_special(
|
||||||
eval_rat = self.get_evaluation_rattrapage(modimpl)
|
modimpl,
|
||||||
if eval_rat:
|
evals_notes_stacked,
|
||||||
notes_rat = self.evals_notes[eval_rat.id].values
|
evals_poids_df,
|
||||||
# remplace les notes invalides (ATT, EXC...) par des NaN
|
Evaluation.EVALUATION_RATTRAPAGE,
|
||||||
notes_rat = np.where(
|
|
||||||
notes_rat > scu.NOTES_ABSENCE,
|
|
||||||
notes_rat / (eval_rat.note_max / 20.0),
|
|
||||||
np.nan,
|
|
||||||
)
|
)
|
||||||
# "Étend" le rattrapage sur les UE: la note de rattrapage est la même
|
etuds_ue_use_rattrapage = (
|
||||||
# pour toutes les UE mais ne remplace que là où elle est supérieure XXX TODO
|
etuds_moy_module_rat > etuds_moy_module
|
||||||
notes_rat_ues = np.stack([notes_rat] * nb_ues, axis=1)
|
) # etud x UE
|
||||||
# prend le max
|
|
||||||
etuds_use_rattrapage = notes_rat_ues > etuds_moy_module
|
|
||||||
etuds_moy_module = np.where(
|
etuds_moy_module = np.where(
|
||||||
etuds_use_rattrapage, notes_rat_ues, etuds_moy_module
|
etuds_ue_use_rattrapage, etuds_moy_module_rat, etuds_moy_module
|
||||||
)
|
)
|
||||||
# Serie indiquant que l'étudiant utilise une note de rattrapage sur l'une des UE:
|
|
||||||
self.etuds_use_rattrapage = pd.Series(
|
self.etuds_use_rattrapage = pd.Series(
|
||||||
etuds_use_rattrapage.any(axis=1), index=self.evals_notes.index
|
np.any(etuds_ue_use_rattrapage, axis=1), index=self.evals_notes.index
|
||||||
)
|
)
|
||||||
# Application des évaluations bonus:
|
# Application des évaluations bonus:
|
||||||
etuds_moy_module = self.apply_bonus(
|
etuds_moy_module = self.apply_bonus(
|
||||||
@ -487,6 +467,34 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
)
|
)
|
||||||
return self.etuds_moy_module
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
def _compute_moy_special(
|
||||||
|
self,
|
||||||
|
modimpl: ModuleImpl,
|
||||||
|
evals_notes_stacked: np.array,
|
||||||
|
evals_poids_df: pd.DataFrame,
|
||||||
|
evaluation_type: int,
|
||||||
|
) -> np.array:
|
||||||
|
"""Calcul moyenne APC sur évals rattrapage ou session2"""
|
||||||
|
nb_etuds = self.evals_notes.shape[0]
|
||||||
|
nb_ues = evals_poids_df.shape[1]
|
||||||
|
evals_coefs_s2 = self.get_evaluations_special_coefs(
|
||||||
|
modimpl, evaluation_type=evaluation_type
|
||||||
|
)
|
||||||
|
evals_poids_s2 = evals_poids_df.values * evals_coefs_s2
|
||||||
|
poids_stacked_s2 = np.stack(
|
||||||
|
[evals_poids_s2] * nb_etuds
|
||||||
|
) # nb_etuds, nb_evals, nb_ues
|
||||||
|
evals_poids_etuds_s2 = np.where(
|
||||||
|
np.stack([self.evals_notes.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
||||||
|
poids_stacked_s2,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
|
etuds_moy_module_s2 = np.sum(
|
||||||
|
evals_poids_etuds_s2 * evals_notes_stacked, axis=1
|
||||||
|
) / np.sum(evals_poids_etuds_s2, axis=1)
|
||||||
|
return etuds_moy_module_s2
|
||||||
|
|
||||||
def apply_bonus(
|
def apply_bonus(
|
||||||
self,
|
self,
|
||||||
etuds_moy_module: pd.DataFrame,
|
etuds_moy_module: pd.DataFrame,
|
||||||
@ -628,24 +636,14 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
|||||||
evals_coefs_etuds * evals_notes_20, axis=1
|
evals_coefs_etuds * evals_notes_20, axis=1
|
||||||
) / np.sum(evals_coefs_etuds, axis=1)
|
) / np.sum(evals_coefs_etuds, axis=1)
|
||||||
|
|
||||||
# Session2 : quand elle existe, remplace la note de module
|
|
||||||
evals_session2 = self.get_evaluations_session2(modimpl)
|
evals_session2 = self.get_evaluations_session2(modimpl)
|
||||||
|
evals_rat = self.get_evaluations_rattrapage(modimpl)
|
||||||
if evals_session2:
|
if evals_session2:
|
||||||
# Calculer la moyenne des évaluations de session2
|
# Session2 : quand elle existe, remplace la note de module
|
||||||
# n'utilise que les notes valides et ABS (0).
|
# Calcule la moyenne des évaluations de session2
|
||||||
# Même calcul que pour les évals normales, mais avec seulement les
|
etuds_moy_module_s2 = self._compute_moy_special(
|
||||||
# coefs des évals de session 2:
|
modimpl, evals_notes_20, Evaluation.EVALUATION_SESSION2
|
||||||
evals_coefs_s2 = self.get_evaluations_session2_coefs(modimpl).reshape(-1)
|
|
||||||
coefs_stacked_s2 = np.stack([evals_coefs_s2] * nb_etuds)
|
|
||||||
# zéro partout sauf si une note ou ABS:
|
|
||||||
evals_coefs_etuds_s2 = np.where(
|
|
||||||
self.evals_notes.values > scu.NOTES_NEUTRALISE, coefs_stacked_s2, 0
|
|
||||||
)
|
)
|
||||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
|
||||||
etuds_moy_module_s2 = np.sum(
|
|
||||||
evals_coefs_etuds_s2 * evals_notes_20, axis=1
|
|
||||||
) / np.sum(evals_coefs_etuds_s2, axis=1)
|
|
||||||
|
|
||||||
etuds_use_session2 = np.isfinite(etuds_moy_module_s2)
|
etuds_use_session2 = np.isfinite(etuds_moy_module_s2)
|
||||||
etuds_moy_module = np.where(
|
etuds_moy_module = np.where(
|
||||||
etuds_use_session2,
|
etuds_use_session2,
|
||||||
@ -655,21 +653,15 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
|||||||
self.etuds_use_session2 = pd.Series(
|
self.etuds_use_session2 = pd.Series(
|
||||||
etuds_use_session2, index=self.evals_notes.index
|
etuds_use_session2, index=self.evals_notes.index
|
||||||
)
|
)
|
||||||
else:
|
elif evals_rat:
|
||||||
# Rattrapage: remplace la note de module ssi elle est supérieure
|
# Rattrapage: remplace la note de module ssi elle est supérieure
|
||||||
eval_rat = self.get_evaluation_rattrapage(modimpl)
|
# Calcule la moyenne des évaluations de rattrapage
|
||||||
if eval_rat:
|
etuds_moy_module_rat = self._compute_moy_special(
|
||||||
notes_rat = self.evals_notes[eval_rat.id].values
|
modimpl, evals_notes_20, Evaluation.EVALUATION_RATTRAPAGE
|
||||||
# remplace les notes invalides (ATT, EXC...) par des NaN
|
|
||||||
notes_rat = np.where(
|
|
||||||
notes_rat > scu.NOTES_ABSENCE,
|
|
||||||
notes_rat / (eval_rat.note_max / 20.0),
|
|
||||||
np.nan,
|
|
||||||
)
|
)
|
||||||
# prend le max
|
etuds_use_rattrapage = etuds_moy_module_rat > etuds_moy_module
|
||||||
etuds_use_rattrapage = notes_rat > etuds_moy_module
|
|
||||||
etuds_moy_module = np.where(
|
etuds_moy_module = np.where(
|
||||||
etuds_use_rattrapage, notes_rat, etuds_moy_module
|
etuds_use_rattrapage, etuds_moy_module_rat, etuds_moy_module
|
||||||
)
|
)
|
||||||
self.etuds_use_rattrapage = pd.Series(
|
self.etuds_use_rattrapage = pd.Series(
|
||||||
etuds_use_rattrapage, index=self.evals_notes.index
|
etuds_use_rattrapage, index=self.evals_notes.index
|
||||||
@ -688,6 +680,28 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
|||||||
|
|
||||||
return self.etuds_moy_module
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
def _compute_moy_special(
|
||||||
|
self, modimpl: ModuleImpl, evals_notes_20: np.array, evaluation_type: int
|
||||||
|
) -> np.array:
|
||||||
|
"""Calcul moyenne sur évals rattrapage ou session2"""
|
||||||
|
# n'utilise que les notes valides et ABS (0).
|
||||||
|
# Même calcul que pour les évals normales, mais avec seulement les
|
||||||
|
# coefs des évals de session 2 ou rattrapage:
|
||||||
|
nb_etuds = self.evals_notes.shape[0]
|
||||||
|
evals_coefs = self.get_evaluations_special_coefs(
|
||||||
|
modimpl, evaluation_type=evaluation_type
|
||||||
|
).reshape(-1)
|
||||||
|
coefs_stacked = np.stack([evals_coefs] * nb_etuds)
|
||||||
|
# zéro partout sauf si une note ou ABS:
|
||||||
|
evals_coefs_etuds = np.where(
|
||||||
|
self.evals_notes.values > scu.NOTES_NEUTRALISE, coefs_stacked, 0
|
||||||
|
)
|
||||||
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
|
etuds_moy_module = np.sum(
|
||||||
|
evals_coefs_etuds * evals_notes_20, axis=1
|
||||||
|
) / np.sum(evals_coefs_etuds, axis=1)
|
||||||
|
return etuds_moy_module # array 1d (nb_etuds)
|
||||||
|
|
||||||
def apply_bonus(
|
def apply_bonus(
|
||||||
self,
|
self,
|
||||||
etuds_moy_module: np.ndarray,
|
etuds_moy_module: np.ndarray,
|
||||||
|
@ -81,8 +81,9 @@ def test_notes_rattrapage(test_client):
|
|||||||
mod_res = res.modimpls_results[moduleimpl_id]
|
mod_res = res.modimpls_results[moduleimpl_id]
|
||||||
moduleimpl = db.session.get(ModuleImpl, moduleimpl_id)
|
moduleimpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||||
# retrouve l'éval. de rattrapage:
|
# retrouve l'éval. de rattrapage:
|
||||||
eval_rat = mod_res.get_evaluation_rattrapage(moduleimpl)
|
evals_rat = mod_res.get_evaluations_rattrapage(moduleimpl)
|
||||||
assert eval_rat.id == e_rat["id"]
|
assert len(evals_rat) == 1
|
||||||
|
assert evals_rat[0].id == e_rat["id"]
|
||||||
# Les deux évaluations sont considérées comme complètes:
|
# Les deux évaluations sont considérées comme complètes:
|
||||||
assert len(mod_res.get_evaluations_completes(moduleimpl)) == 2
|
assert len(mod_res.get_evaluations_completes(moduleimpl)) == 2
|
||||||
|
|
||||||
@ -176,18 +177,25 @@ def test_notes_rattrapage(test_client):
|
|||||||
# Note moyenne: utilise session 2 même si inférieure
|
# Note moyenne: utilise session 2 même si inférieure
|
||||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(20.0)
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(20.0)
|
||||||
|
|
||||||
|
# Met la note session2 à ABS (None)
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=None
|
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=None
|
||||||
)
|
)
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
sem["formsemestre_id"], etud["etudid"]
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
)
|
)
|
||||||
# Note moyenne: revient à note normale
|
# Note moyenne: zéro car ABS
|
||||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(0.0)
|
||||||
# Supprime évaluation session 2
|
# Supprime note session 2
|
||||||
_, _, _ = G.create_note(
|
_, _, _ = G.create_note(
|
||||||
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=scu.NOTES_SUPPRESS
|
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=scu.NOTES_SUPPRESS
|
||||||
)
|
)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
# Note moyenne: revient à sa valeur initiale, 10/20
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
||||||
|
# Supprime évaluation session 2
|
||||||
evaluation = db.session.get(Evaluation, e_session2["id"])
|
evaluation = db.session.get(Evaluation, e_session2["id"])
|
||||||
assert evaluation
|
assert evaluation
|
||||||
evaluation.delete()
|
evaluation.delete()
|
||||||
|
Loading…
Reference in New Issue
Block a user