forked from ScoDoc/ScoDoc
WIP: calcul des moyennes de modules BUT
This commit is contained in:
parent
3a0a2382c8
commit
a83ab8f684
@ -41,11 +41,14 @@ from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
def df_load_evaluations_poids(moduleimpl_id: int, default_poids=0.0) -> pd.DataFrame:
|
||||
def df_load_evaluations_poids(
|
||||
moduleimpl_id: int, default_poids=1.0
|
||||
) -> tuple[pd.DataFrame, list]:
|
||||
"""Charge poids des évaluations d'un module et retourne un dataframe
|
||||
rows = evaluations, columns = UE, value = poids (float).
|
||||
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
||||
remplies par default_poids.
|
||||
Résultat: (evals_poids, liste de UE du semestre)
|
||||
"""
|
||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
||||
@ -59,7 +62,7 @@ def df_load_evaluations_poids(moduleimpl_id: int, default_poids=0.0) -> pd.DataF
|
||||
df[eval_poids.ue_id][eval_poids.evaluation_id] = eval_poids.poids
|
||||
if default_poids is not None:
|
||||
df.fillna(value=default_poids, inplace=True)
|
||||
return df
|
||||
return df, ues
|
||||
|
||||
|
||||
def check_moduleimpl_conformity(
|
||||
@ -83,12 +86,14 @@ def df_load_modimpl_notes(moduleimpl_id: int) -> pd.DataFrame:
|
||||
colonnes: evaluation_id (le nom de la colonne est l'evaluation_id en str)
|
||||
index (lignes): etudid
|
||||
|
||||
Résultat: (evals_notes, liste de évaluations du moduleimpl)
|
||||
|
||||
L'ensemble des étudiants est celui des inscrits au module.
|
||||
|
||||
Valeurs des notes:
|
||||
note : float (valeur enregistrée brute, pas normalisée sur 20)
|
||||
Les notes renvoyées sont "brutes" et peuvent prendre els valeurs:
|
||||
note : float (valeur enregistrée brute, non normalisée sur 20)
|
||||
pas de note: NaN
|
||||
absent: 0.
|
||||
absent: NaN
|
||||
excusé: NOTES_NEUTRALISE (voir sco_utils)
|
||||
attente: NOTES_ATTENTE
|
||||
|
||||
@ -96,7 +101,7 @@ def df_load_modimpl_notes(moduleimpl_id: int) -> pd.DataFrame:
|
||||
"""
|
||||
etudids = [e.etudid for e in ModuleImpl.query.get(moduleimpl_id).inscriptions]
|
||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id)
|
||||
df = pd.DataFrame(index=etudids, dtype=float) # empty df with all students
|
||||
evals_notes = pd.DataFrame(index=etudids, dtype=float) # empty df with all students
|
||||
|
||||
for evaluation in evaluations:
|
||||
eval_df = pd.read_sql(
|
||||
@ -107,14 +112,30 @@ def df_load_modimpl_notes(moduleimpl_id: int) -> pd.DataFrame:
|
||||
params={"evaluation_id": evaluation.evaluation_id},
|
||||
index_col="etudid",
|
||||
)
|
||||
# Remplace les ABS (NULL en BD, donc NaN ici) par des zéros.
|
||||
eval_df.fillna(value=0.0, inplace=True)
|
||||
df = df.merge(eval_df, how="outer", left_index=True, right_index=True)
|
||||
evals_notes = evals_notes.merge(
|
||||
eval_df, how="outer", left_index=True, right_index=True
|
||||
)
|
||||
|
||||
return df
|
||||
return evals_notes, evaluations
|
||||
|
||||
|
||||
def compute_module_moy(evals_notes: pd.DataFrame, evals_poids: pd.DataFrame):
|
||||
def normalize_evals_notes(evals_notes: pd.DataFrame, evaluations: list) -> pd.DataFrame:
|
||||
"""Transforme les notes brutes (en base) en valeurs entre 0 et 20:
|
||||
les notes manquantes, ABS, EXC ATT sont mises à zéro, et les valeurs
|
||||
normalisées entre 0 et 20.
|
||||
Return: notes sur 20"""
|
||||
# Le fillna (pour traiter les ABS) est inutile car le where matche le NaN
|
||||
# eval_df.fillna(value=0.0, inplace=True)
|
||||
return evals_notes.where(evals_notes > -1000, 0) / [
|
||||
e.note_max / 20.0 for e in evaluations
|
||||
]
|
||||
|
||||
|
||||
def compute_module_moy(
|
||||
evals_notes: pd.DataFrame,
|
||||
evals_poids: pd.DataFrame,
|
||||
evals_coefs=1.0,
|
||||
) -> pd.DataFrame:
|
||||
"""Calcule les moyennes des étudiants dans ce module
|
||||
|
||||
- evals_notes : DataFrame, colonnes: EVALS, Lignes: etudid
|
||||
@ -123,6 +144,8 @@ def compute_module_moy(evals_notes: pd.DataFrame, evals_poids: pd.DataFrame):
|
||||
|
||||
- evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||
|
||||
- evals_coefs: sequence, 1 coef par UE
|
||||
|
||||
Résultat: DataFrame, colonnes UE, lignes etud
|
||||
= la note de l'étudiant dans chaque UE pour ce module.
|
||||
ou NaN si les évaluations (dans lesquelles l'étudiant à des notes)
|
||||
@ -131,7 +154,7 @@ def compute_module_moy(evals_notes: pd.DataFrame, evals_poids: pd.DataFrame):
|
||||
nb_etuds = len(evals_notes)
|
||||
nb_ues = evals_poids.shape[1]
|
||||
etud_moy_module_arr = np.zeros((nb_etuds, nb_ues))
|
||||
evals_poids_arr = evals_poids.to_numpy().transpose()
|
||||
evals_poids_arr = evals_poids.to_numpy().transpose() * evals_coefs
|
||||
evals_notes_arr = evals_notes.values # .to_numpy()
|
||||
val_neutres = np.array((scu.NOTES_NEUTRALISE, scu.NOTES_ATTENTE))
|
||||
for i in range(nb_etuds):
|
||||
|
@ -46,7 +46,7 @@ class Evaluation(db.Model):
|
||||
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Evaluation {self.id} {self.jour.isoformat()}"
|
||||
return f"<Evaluation {self.id} {self.jour.isoformat() if self.jour else ''}{self.description[:16] if self.description else ''}"
|
||||
|
||||
def to_dict(self):
|
||||
e = dict(self.__dict__)
|
||||
|
@ -5,6 +5,7 @@ from typing import Any
|
||||
from app import db
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
from app.scodoc import sco_codes_parcours
|
||||
@ -86,6 +87,16 @@ class UniteEns(db.Model):
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}(id={self.id}, formation_id={self.formation_id}, acronyme='{self.acronyme}')>"
|
||||
|
||||
def to_dict(self):
|
||||
"""as a dict, with the same conversions as in ScoDoc7"""
|
||||
e = dict(self.__dict__)
|
||||
e.pop("_sa_instance_state", None)
|
||||
# ScoDoc7 output_formators
|
||||
e["numero"] = e["numero"] if e["numero"] else 0
|
||||
e["ects"] = e["ects"] if e["ects"] else 0.0
|
||||
e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0
|
||||
return e
|
||||
|
||||
def is_locked(self):
|
||||
"""True if UE should not be modified
|
||||
(contains modules used in a locked formsemestre)
|
||||
@ -95,6 +106,20 @@ class UniteEns(db.Model):
|
||||
|
||||
return sco_edit_ue.ue_is_locked(self.id)
|
||||
|
||||
def guess_semestre_idx(self) -> None:
|
||||
"""Lorsqu'on prend une ancienne formation non APC,
|
||||
les UE n'ont pas d'indication de semestre.
|
||||
Cette méthode fixe le semestre en prenant celui du premier module,
|
||||
ou à défaut le met à 1.
|
||||
"""
|
||||
if self.semestre_idx is None:
|
||||
if self.modules:
|
||||
self.semestre_idx = self.modules[0].semestre_id
|
||||
else:
|
||||
self.semestre_idx = 1
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
class Matiere(db.Model):
|
||||
"""Matières: regroupe les modules d'une UE
|
||||
|
@ -587,7 +587,11 @@ def module_edit(module_id=None):
|
||||
),
|
||||
]
|
||||
# force module semestre_idx to its UE
|
||||
module["semestre_id"] = a_module.ue.semestre_idx
|
||||
if a_module.ue.semestre_idx:
|
||||
module["semestre_id"] = a_module.ue.semestre_idx
|
||||
# Filet de sécurité si jamais l'UE n'a pas non plus de semestre:
|
||||
if not module["semestre_id"]:
|
||||
module["semestre_id"] = 1
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
|
@ -467,8 +467,19 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
||||
else:
|
||||
semestre_idx = int(semestre_idx)
|
||||
semestre_ids = range(1, parcours.NB_SEM + 1)
|
||||
ues = ue_list(args={"formation_id": formation_id, "is_external": False})
|
||||
ues_externes = ue_list(args={"formation_id": formation_id, "is_external": True})
|
||||
# transition: on requete ici via l'ORM mais on utilise les fonctions ScoDoc7
|
||||
# basées sur des dicts
|
||||
ues_obj = UniteEns.query.filter_by(formation_id=formation_id, is_external=False)
|
||||
ues_externes_obj = UniteEns.query.filter_by(
|
||||
formation_id=formation_id, is_external=True
|
||||
)
|
||||
if is_apc:
|
||||
# pour faciliter la transition des anciens programmes non APC
|
||||
for ue in ues_obj:
|
||||
ue.guess_semestre_idx()
|
||||
ues = [ue.to_dict() for ue in ues_obj]
|
||||
ues_externes = [ue.to_dict() for ue in ues_externes_obj]
|
||||
|
||||
# tri par semestre et numero:
|
||||
_add_ue_semestre_id(ues, is_apc)
|
||||
_add_ue_semestre_id(ues_externes, is_apc)
|
||||
@ -928,78 +939,6 @@ def _ue_table_matieres(
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _ue_table_ressources_saes(
|
||||
parcours,
|
||||
ue,
|
||||
editable,
|
||||
tag_editable,
|
||||
arrow_up,
|
||||
arrow_down,
|
||||
arrow_none,
|
||||
delete_icon,
|
||||
delete_disabled_icon,
|
||||
module_type=None,
|
||||
):
|
||||
"""Édition de programme: liste des ressources et SAÉs d'une UE.
|
||||
(pour les parcours APC_SAE)
|
||||
"""
|
||||
matieres = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
|
||||
if not matieres:
|
||||
# Les formations APC (BUT) n'utilisent pas de matières
|
||||
# mais il doit y en avoir une par UE
|
||||
# silently fix this on-the-fly to ease migration
|
||||
_ = sco_edit_matiere.do_matiere_create(
|
||||
{"ue_id": ue["ue_id"], "titre": "APC", "numero": 1},
|
||||
)
|
||||
matieres = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
|
||||
assert matieres
|
||||
mat = matieres[0]
|
||||
|
||||
H = [
|
||||
"""
|
||||
<ul class="notes_matiere_list but_matiere_list">
|
||||
"""
|
||||
]
|
||||
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
||||
for titre, element_name, element_type in (
|
||||
("Ressources", "ressource", scu.ModuleType.RESSOURCE),
|
||||
("SAÉs", "SAÉ", scu.ModuleType.SAE),
|
||||
("Autres modules", "xxx", None),
|
||||
):
|
||||
H.append(f'<li class="notes_matiere_list">{titre}')
|
||||
elements = [
|
||||
m
|
||||
for m in modules
|
||||
if element_type == m["module_type"]
|
||||
or (
|
||||
(element_type is None)
|
||||
and m["module_type"]
|
||||
not in (scu.ModuleType.RESSOURCE, scu.ModuleType.SAE)
|
||||
)
|
||||
]
|
||||
H.append(
|
||||
_ue_table_modules(
|
||||
parcours,
|
||||
mat,
|
||||
elements,
|
||||
editable,
|
||||
tag_editable,
|
||||
arrow_up,
|
||||
arrow_down,
|
||||
arrow_none,
|
||||
delete_icon,
|
||||
delete_disabled_icon,
|
||||
module_type=module_type,
|
||||
empty_list_msg="Aucune " + element_name,
|
||||
create_element_msg="créer une " + element_name,
|
||||
add_suppress_link=False,
|
||||
)
|
||||
)
|
||||
|
||||
H.append("</li></ul>")
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _ue_table_modules(
|
||||
parcours,
|
||||
mat,
|
||||
|
@ -31,9 +31,11 @@
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
|
||||
from app import models
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
from app.comp import moy_mod
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_edit_module
|
||||
@ -204,6 +206,7 @@ def do_evaluation_listenotes():
|
||||
group_ids=tf[2]["group_ids"],
|
||||
hide_groups=hide_groups,
|
||||
with_emails=with_emails,
|
||||
mode=mode,
|
||||
)
|
||||
|
||||
|
||||
@ -216,15 +219,22 @@ def _make_table_notes(
|
||||
hide_groups=False,
|
||||
with_emails=False,
|
||||
group_ids=[],
|
||||
mode="module", # "eval" or "module"
|
||||
):
|
||||
"""Table liste notes (une seule évaluation ou toutes celles d'un module)"""
|
||||
# Code à ré-écrire !
|
||||
if not evals:
|
||||
return "<p>Aucune évaluation !</p>"
|
||||
E = evals[0]
|
||||
moduleimpl_id = E["moduleimpl_id"]
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
module = models.Module.query.get(modimpl["module_id"])
|
||||
is_apc = module.formation.get_parcours().APC_SAE
|
||||
if is_apc:
|
||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(moduleimpl_id)
|
||||
else:
|
||||
evals_poids, ues = None, None
|
||||
sem = sco_formsemestre.get_formsemestre(modimpl["formsemestre_id"])
|
||||
# (debug) check that all evals are in same module:
|
||||
for e in evals:
|
||||
if e["moduleimpl_id"] != moduleimpl_id:
|
||||
@ -236,16 +246,12 @@ def _make_table_notes(
|
||||
keep_numeric = False
|
||||
# Si pas de groupe, affiche tout
|
||||
if not group_ids:
|
||||
group_ids = [sco_groups.get_default_group(M["formsemestre_id"])]
|
||||
group_ids = [sco_groups.get_default_group(modimpl["formsemestre_id"])]
|
||||
groups = sco_groups.listgroups(group_ids)
|
||||
|
||||
gr_title = sco_groups.listgroups_abbrev(groups)
|
||||
gr_title_filename = sco_groups.listgroups_filename(groups)
|
||||
|
||||
etudid_etats = sco_groups.do_evaluation_listeetuds_groups(
|
||||
E["evaluation_id"], groups, include_dems=True
|
||||
)
|
||||
|
||||
if anonymous_listing:
|
||||
columns_ids = ["code"] # cols in table
|
||||
else:
|
||||
@ -269,7 +275,7 @@ def _make_table_notes(
|
||||
|
||||
rows = []
|
||||
|
||||
class keymgr(dict): # comment : key (pour regrouper les comments a la fin)
|
||||
class KeyManager(dict): # comment : key (pour regrouper les comments a la fin)
|
||||
def __init__(self):
|
||||
self.lastkey = 1
|
||||
|
||||
@ -279,7 +285,19 @@ def _make_table_notes(
|
||||
# self.lastkey = chr(ord(self.lastkey)+1)
|
||||
return str(r)
|
||||
|
||||
K = keymgr()
|
||||
key_mgr = KeyManager()
|
||||
|
||||
# code pour listings anonyme, à la place du nom
|
||||
if sco_preferences.get_preference("anonymous_lst_code") == "INE":
|
||||
anonymous_lst_key = "code_ine"
|
||||
elif sco_preferences.get_preference("anonymous_lst_code") == "NIP":
|
||||
anonymous_lst_key = "code_nip"
|
||||
else:
|
||||
anonymous_lst_key = "etudid"
|
||||
|
||||
etudid_etats = sco_groups.do_evaluation_listeetuds_groups(
|
||||
E["evaluation_id"], groups, include_dems=True
|
||||
)
|
||||
for etudid, etat in etudid_etats:
|
||||
css_row_class = None
|
||||
# infos identite etudiant
|
||||
@ -295,11 +313,7 @@ def _make_table_notes(
|
||||
else:
|
||||
grc = etat
|
||||
|
||||
code = "" # code pour listings anonyme, à la place du nom
|
||||
if sco_preferences.get_preference("anonymous_lst_code") == "INE":
|
||||
code = etud["code_ine"]
|
||||
elif sco_preferences.get_preference("anonymous_lst_code") == "NIP":
|
||||
code = etud["code_nip"]
|
||||
code = etud.get(anonymous_lst_key)
|
||||
if not code: # laisser le code vide n'aurait aucun sens, prenons l'etudid
|
||||
code = etudid
|
||||
|
||||
@ -310,7 +324,7 @@ def _make_table_notes(
|
||||
"etudid": etudid,
|
||||
"nom": etud["nom"].upper(),
|
||||
"_nomprenom_target": "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s"
|
||||
% (M["formsemestre_id"], etudid),
|
||||
% (modimpl["formsemestre_id"], etudid),
|
||||
"_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]),
|
||||
"prenom": etud["prenom"].lower().capitalize(),
|
||||
"nomprenom": etud["nomprenom"],
|
||||
@ -322,7 +336,7 @@ def _make_table_notes(
|
||||
)
|
||||
|
||||
# Lignes en tête:
|
||||
coefs = {
|
||||
row_coefs = {
|
||||
"nom": "",
|
||||
"prenom": "",
|
||||
"nomprenom": "",
|
||||
@ -331,7 +345,16 @@ def _make_table_notes(
|
||||
"_css_row_class": "sorttop fontitalic",
|
||||
"_table_part": "head",
|
||||
}
|
||||
note_max = {
|
||||
row_poids = {
|
||||
"nom": "",
|
||||
"prenom": "",
|
||||
"nomprenom": "",
|
||||
"group": "",
|
||||
"code": "",
|
||||
"_css_row_class": "sorttop poids",
|
||||
"_table_part": "head",
|
||||
}
|
||||
row_note_max = {
|
||||
"nom": "",
|
||||
"prenom": "",
|
||||
"nomprenom": "",
|
||||
@ -340,7 +363,7 @@ def _make_table_notes(
|
||||
"_css_row_class": "sorttop fontitalic",
|
||||
"_table_part": "head",
|
||||
}
|
||||
moys = {
|
||||
row_moys = {
|
||||
"_css_row_class": "moyenne sortbottom",
|
||||
"_table_part": "foot",
|
||||
#'_nomprenom_td_attrs' : 'colspan="2" ',
|
||||
@ -352,12 +375,16 @@ def _make_table_notes(
|
||||
e["eval_state"] = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
||||
notes, nb_abs, nb_att = _add_eval_columns(
|
||||
e,
|
||||
evals_poids,
|
||||
ues,
|
||||
rows,
|
||||
titles,
|
||||
coefs,
|
||||
note_max,
|
||||
moys,
|
||||
K,
|
||||
row_coefs,
|
||||
row_poids,
|
||||
row_note_max,
|
||||
row_moys,
|
||||
is_apc,
|
||||
key_mgr,
|
||||
note_sur_20,
|
||||
keep_numeric,
|
||||
)
|
||||
@ -370,28 +397,51 @@ def _make_table_notes(
|
||||
key=lambda x: (x["nom"] or "", x["prenom"] or "")
|
||||
) # sort by nom, prenom
|
||||
|
||||
# Si module, ajoute moyenne du module:
|
||||
if len(evals) > 1:
|
||||
_add_moymod_column(
|
||||
sem["formsemestre_id"],
|
||||
e,
|
||||
rows,
|
||||
titles,
|
||||
coefs,
|
||||
note_max,
|
||||
moys,
|
||||
note_sur_20,
|
||||
keep_numeric,
|
||||
)
|
||||
columns_ids.append("moymod")
|
||||
# Si module, ajoute la (les) "moyenne(s) du module:
|
||||
if mode == "module":
|
||||
if len(evals) > 1:
|
||||
# Moyenne de l'étudant dans le module
|
||||
# Affichée même en APC à titre indicatif
|
||||
_add_moymod_column(
|
||||
sem["formsemestre_id"],
|
||||
moduleimpl_id,
|
||||
rows,
|
||||
columns_ids,
|
||||
titles,
|
||||
row_coefs,
|
||||
row_poids,
|
||||
row_note_max,
|
||||
row_moys,
|
||||
is_apc,
|
||||
keep_numeric,
|
||||
)
|
||||
if is_apc:
|
||||
# Ajoute une colonne par UE
|
||||
_add_apc_columns(
|
||||
moduleimpl_id,
|
||||
evals_poids,
|
||||
ues,
|
||||
rows,
|
||||
columns_ids,
|
||||
titles,
|
||||
row_coefs,
|
||||
row_poids,
|
||||
row_note_max,
|
||||
row_moys,
|
||||
keep_numeric,
|
||||
)
|
||||
|
||||
# Ajoute colonnes emails tout à droite:
|
||||
if with_emails:
|
||||
columns_ids += ["email", "emailperso"]
|
||||
# Ajoute lignes en tête et moyennes
|
||||
if len(evals) > 0:
|
||||
rows = [coefs, note_max] + rows
|
||||
rows.append(moys)
|
||||
rows_head = [row_coefs]
|
||||
if is_apc:
|
||||
rows_head.append(row_poids)
|
||||
rows_head.append(row_note_max)
|
||||
rows = rows_head + rows
|
||||
rows.append(row_moys)
|
||||
# ajout liens HTMl vers affichage une evaluation:
|
||||
if format == "html" and len(evals) > 1:
|
||||
rlinks = {"_table_part": "head"}
|
||||
@ -425,7 +475,7 @@ def _make_table_notes(
|
||||
if with_emails:
|
||||
gl = "&with_emails%3Alist=yes" + gl
|
||||
if len(evals) == 1:
|
||||
evalname = "%s-%s" % (Mod["code"], ndb.DateDMYtoISO(E["jour"]))
|
||||
evalname = "%s-%s" % (module.code, ndb.DateDMYtoISO(E["jour"]))
|
||||
hh = "%s, %s (%d étudiants)" % (E["description"], gr_title, len(etudid_etats))
|
||||
filename = scu.make_filename("notes_%s_%s" % (evalname, gr_title_filename))
|
||||
caption = hh
|
||||
@ -437,8 +487,8 @@ def _make_table_notes(
|
||||
% (nb_abs, nb_att)
|
||||
)
|
||||
else:
|
||||
filename = scu.make_filename("notes_%s_%s" % (Mod["code"], gr_title_filename))
|
||||
title = "Notes du module %(code)s %(titre)s" % Mod
|
||||
filename = scu.make_filename("notes_%s_%s" % (module.code, gr_title_filename))
|
||||
title = f"Notes {module.type_name()} {module.code} {module.titre}"
|
||||
title += " semestre %(titremois)s" % sem
|
||||
if gr_title and gr_title != "tous":
|
||||
title += " %s" % gr_title
|
||||
@ -447,10 +497,11 @@ def _make_table_notes(
|
||||
if format == "pdf":
|
||||
caption = "" # same as pdf_title
|
||||
pdf_title = title
|
||||
html_title = (
|
||||
"""<h2 class="formsemestre">Notes du module <a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a></h2>"""
|
||||
% (moduleimpl_id, Mod["code"], Mod["titre"])
|
||||
)
|
||||
html_title = f"""<h2 class="formsemestre">Notes {module.type_name()} <a href="{
|
||||
url_for("notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
||||
}">{module.code} {module.titre}</a></h2>
|
||||
"""
|
||||
base_url = "evaluation_listenotes?moduleimpl_id=%s" % moduleimpl_id + gl
|
||||
# display
|
||||
tab = GenTable(
|
||||
@ -469,7 +520,7 @@ def _make_table_notes(
|
||||
html_title=html_title,
|
||||
pdf_title=pdf_title,
|
||||
html_class="table_leftalign notes_evaluation",
|
||||
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
||||
preferences=sco_preferences.SemPreferences(modimpl["formsemestre_id"]),
|
||||
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
||||
)
|
||||
|
||||
@ -497,7 +548,7 @@ def _make_table_notes(
|
||||
+ "</div></td>\n",
|
||||
'<td style="padding-left: 50px; vertical-align: top;"><p>',
|
||||
]
|
||||
commentkeys = list(K.items()) # [ (comment, key), ... ]
|
||||
commentkeys = list(key_mgr.items()) # [ (comment, key), ... ]
|
||||
commentkeys.sort(key=lambda x: int(x[1]))
|
||||
for (comment, key) in commentkeys:
|
||||
C.append(
|
||||
@ -526,7 +577,19 @@ def _make_table_notes(
|
||||
|
||||
|
||||
def _add_eval_columns(
|
||||
e, rows, titles, coefs, note_max, moys, K, note_sur_20, keep_numeric
|
||||
e,
|
||||
evals_poids,
|
||||
ues,
|
||||
rows,
|
||||
titles,
|
||||
row_coefs,
|
||||
row_poids,
|
||||
row_note_max,
|
||||
row_moys,
|
||||
is_apc,
|
||||
K,
|
||||
note_sur_20,
|
||||
keep_numeric,
|
||||
):
|
||||
"""Add eval e"""
|
||||
nb_notes = 0
|
||||
@ -594,19 +657,23 @@ def _add_eval_columns(
|
||||
}
|
||||
)
|
||||
|
||||
coefs[evaluation_id] = "coef. %s" % e["coefficient"]
|
||||
row_coefs[evaluation_id] = "coef. %s" % e["coefficient"]
|
||||
if is_apc:
|
||||
row_poids[evaluation_id] = _mini_table_eval_ue_poids(
|
||||
evaluation_id, evals_poids, ues
|
||||
)
|
||||
if note_sur_20:
|
||||
nmax = 20.0
|
||||
else:
|
||||
nmax = e["note_max"]
|
||||
if keep_numeric:
|
||||
note_max[evaluation_id] = nmax
|
||||
row_note_max[evaluation_id] = nmax
|
||||
else:
|
||||
note_max[evaluation_id] = "/ %s" % nmax
|
||||
row_note_max[evaluation_id] = "/ %s" % nmax
|
||||
|
||||
if nb_notes > 0:
|
||||
moys[evaluation_id] = "%.3g" % (sum_notes / nb_notes)
|
||||
moys[
|
||||
row_moys[evaluation_id] = "%.3g" % (sum_notes / nb_notes)
|
||||
row_moys[
|
||||
"_" + str(evaluation_id) + "_help"
|
||||
] = "moyenne sur %d notes (%s le %s)" % (
|
||||
nb_notes,
|
||||
@ -614,7 +681,7 @@ def _add_eval_columns(
|
||||
e["jour"],
|
||||
)
|
||||
else:
|
||||
moys[evaluation_id] = ""
|
||||
row_moys[evaluation_id] = ""
|
||||
|
||||
titles[evaluation_id] = "%(description)s (%(jour)s)" % e
|
||||
|
||||
@ -628,15 +695,29 @@ def _add_eval_columns(
|
||||
return notes, nb_abs, nb_att # pour histogramme
|
||||
|
||||
|
||||
def _mini_table_eval_ue_poids(evaluation_id, evals_poids, ues):
|
||||
"contenu de la cellule: poids"
|
||||
return (
|
||||
"""<table class="eval_poids" title="poids vers les UE"><tr><td>"""
|
||||
+ "</td><td>".join([f"{ue.acronyme}" for ue in ues])
|
||||
+ "</td></tr>"
|
||||
+ "<tr><td>"
|
||||
+ "</td><td>".join([f"{evals_poids[ue.id][evaluation_id]}" for ue in ues])
|
||||
+ "</td></tr></table>"
|
||||
)
|
||||
|
||||
|
||||
def _add_moymod_column(
|
||||
formsemestre_id,
|
||||
e,
|
||||
moduleimpl_id,
|
||||
rows,
|
||||
columns_ids,
|
||||
titles,
|
||||
coefs,
|
||||
note_max,
|
||||
moys,
|
||||
note_sur_20,
|
||||
row_coefs,
|
||||
row_poids,
|
||||
row_note_max,
|
||||
row_moys,
|
||||
is_apc,
|
||||
keep_numeric,
|
||||
):
|
||||
"""Ajoute la colonne moymod à rows"""
|
||||
@ -647,23 +728,61 @@ def _add_moymod_column(
|
||||
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
|
||||
for row in rows:
|
||||
etudid = row["etudid"]
|
||||
val = nt.get_etud_mod_moy(
|
||||
e["moduleimpl_id"], etudid
|
||||
) # note sur 20, ou 'NA','NI'
|
||||
val = nt.get_etud_mod_moy(moduleimpl_id, etudid) # note sur 20, ou 'NA','NI'
|
||||
row[col_id] = scu.fmt_note(val, keep_numeric=keep_numeric)
|
||||
row["_" + col_id + "_td_attrs"] = ' class="moyenne" '
|
||||
if not isinstance(val, str):
|
||||
notes.append(val)
|
||||
nb_notes = nb_notes + 1
|
||||
sum_notes += val
|
||||
coefs[col_id] = "(avec abs)"
|
||||
row_coefs[col_id] = "(avec abs)"
|
||||
if is_apc:
|
||||
row_poids[col_id] = "à titre indicatif"
|
||||
if keep_numeric:
|
||||
note_max[col_id] = 20.0
|
||||
row_note_max[col_id] = 20.0
|
||||
else:
|
||||
note_max[col_id] = "/ 20"
|
||||
row_note_max[col_id] = "/ 20"
|
||||
titles[col_id] = "Moyenne module"
|
||||
columns_ids.append(col_id)
|
||||
if nb_notes > 0:
|
||||
moys[col_id] = "%.3g" % (sum_notes / nb_notes)
|
||||
moys["_" + col_id + "_help"] = "moyenne des moyennes"
|
||||
row_moys[col_id] = "%.3g" % (sum_notes / nb_notes)
|
||||
row_moys["_" + col_id + "_help"] = "moyenne des moyennes"
|
||||
else:
|
||||
moys[col_id] = ""
|
||||
row_moys[col_id] = ""
|
||||
|
||||
|
||||
def _add_apc_columns(
|
||||
moduleimpl_id,
|
||||
evals_poids,
|
||||
ues,
|
||||
rows,
|
||||
columns_ids,
|
||||
titles,
|
||||
row_coefs,
|
||||
row_poids,
|
||||
row_note_max,
|
||||
row_moys,
|
||||
keep_numeric,
|
||||
):
|
||||
"""Ajoute les colonnes moyennes vers les UE"""
|
||||
# On raccorde ici les nouveaux calculs de notes (BUT 2021)
|
||||
# sur l'ancien code ScoDoc
|
||||
# => On recharge tout dans les nouveaux modèles
|
||||
# rows est une liste de dict avec une clé "etudid"
|
||||
# on va y ajouter une clé par UE du semestre
|
||||
|
||||
evals_notes, evaluations = moy_mod.df_load_modimpl_notes(moduleimpl_id)
|
||||
evals_notes_sur_20 = moy_mod.normalize_evals_notes(evals_notes, evaluations)
|
||||
etud_moy_module = moy_mod.compute_module_moy(
|
||||
evals_notes_sur_20, evals_poids, [e.coefficient for e in evaluations]
|
||||
)
|
||||
|
||||
for row in rows:
|
||||
for ue in ues:
|
||||
moy_ue = etud_moy_module[ue.id].get(row["etudid"], "?")
|
||||
row[f"moy_ue_{ue.id}"] = scu.fmt_note(moy_ue, keep_numeric=keep_numeric)
|
||||
row[f"_moy_ue_{ue.id}_class"] = "moy_ue"
|
||||
for ue in ues:
|
||||
col_id = f"moy_ue_{ue.id}"
|
||||
titles[col_id] = ue.acronyme
|
||||
columns_ids.append(col_id)
|
||||
|
@ -255,7 +255,7 @@ def do_evaluation_upload_xls():
|
||||
diag.append("Notes invalides pour: " + ", ".join(etudsnames))
|
||||
raise InvalidNoteValue()
|
||||
else:
|
||||
nb_changed, nb_suppress, existing_decisions = _notes_add(
|
||||
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||
authuser, evaluation_id, L, comment
|
||||
)
|
||||
# news
|
||||
@ -345,7 +345,7 @@ def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
|
||||
)
|
||||
# ok
|
||||
comment = "Initialisation notes manquantes"
|
||||
nb_changed, _, _ = _notes_add(current_user, evaluation_id, L, comment)
|
||||
nb_changed, _, _ = notes_add(current_user, evaluation_id, L, comment)
|
||||
# news
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||
@ -407,7 +407,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
notes = [(etudid, scu.NOTES_SUPPRESS) for etudid in NotesDB.keys()]
|
||||
|
||||
if not dialog_confirmed:
|
||||
nb_changed, nb_suppress, existing_decisions = _notes_add(
|
||||
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||
current_user, evaluation_id, notes, do_it=False
|
||||
)
|
||||
msg = (
|
||||
@ -425,7 +425,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
)
|
||||
|
||||
# modif
|
||||
nb_changed, nb_suppress, existing_decisions = _notes_add(
|
||||
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||
current_user, evaluation_id, notes, comment="effacer tout"
|
||||
)
|
||||
assert nb_changed == nb_suppress
|
||||
@ -454,7 +454,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
return html_sco_header.sco_header() + "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
||||
def notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True) -> tuple:
|
||||
"""
|
||||
Insert or update notes
|
||||
notes is a list of tuples (etudid,value)
|
||||
@ -462,7 +462,7 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
||||
WOULD be changed or suppressed.
|
||||
Nota:
|
||||
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
|
||||
Return number of changed notes
|
||||
Return tuple (nb_changed, nb_suppress, existing_decisions)
|
||||
"""
|
||||
now = psycopg2.Timestamp(
|
||||
*time.localtime()[:6]
|
||||
@ -563,7 +563,7 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
||||
else: # suppression ancienne note
|
||||
if do_it:
|
||||
log(
|
||||
"_notes_add, suppress, evaluation_id=%s, etudid=%s, oldval=%s"
|
||||
"notes_add, suppress, evaluation_id=%s, etudid=%s, oldval=%s"
|
||||
% (evaluation_id, etudid, oldval)
|
||||
)
|
||||
cursor.execute(
|
||||
@ -587,7 +587,7 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
||||
if has_existing_decision(M, E, etudid):
|
||||
existing_decisions.append(etudid)
|
||||
except:
|
||||
log("*** exception in _notes_add")
|
||||
log("*** exception in notes_add")
|
||||
if do_it:
|
||||
cnx.rollback() # abort
|
||||
# inval cache
|
||||
@ -1265,7 +1265,7 @@ def save_note(etudid=None, evaluation_id=None, value=None, comment=""):
|
||||
else:
|
||||
L, _, _, _, _ = _check_notes([(etudid, value)], E, Mod)
|
||||
if L:
|
||||
nbchanged, _, existing_decisions = _notes_add(
|
||||
nbchanged, _, existing_decisions = notes_add(
|
||||
authuser, evaluation_id, L, comment=comment, do_it=True
|
||||
)
|
||||
sco_news.add(
|
||||
|
@ -167,7 +167,7 @@ def external_ue_inscrit_et_note(moduleimpl_id, formsemestre_id, notes_etuds):
|
||||
description="note externe",
|
||||
)
|
||||
# Saisie des notes
|
||||
_, _, _ = sco_saisie_notes._notes_add(
|
||||
_, _, _ = sco_saisie_notes.notes_add(
|
||||
current_user,
|
||||
evaluation_id,
|
||||
list(notes_etuds.items()),
|
||||
|
@ -44,6 +44,7 @@ import unicodedata
|
||||
import urllib
|
||||
from urllib.parse import urlparse, parse_qsl, urlunparse, urlencode
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image as PILImage
|
||||
import pydot
|
||||
import requests
|
||||
@ -138,6 +139,8 @@ def fmt_note(val, note_max=None, keep_numeric=False):
|
||||
if val == NOTES_ATTENTE:
|
||||
return "ATT" # attente, note neutralisee
|
||||
if isinstance(val, float) or isinstance(val, int):
|
||||
if np.isnan(val):
|
||||
return "/"
|
||||
if note_max != None and note_max > 0:
|
||||
val = val * 20.0 / note_max
|
||||
if keep_numeric:
|
||||
|
@ -1040,6 +1040,14 @@ td.colcomment, span.colcomment {
|
||||
color: rgb(80,100,80);
|
||||
}
|
||||
|
||||
table.notes_evaluation table.eval_poids {
|
||||
font-size: 50%;
|
||||
}
|
||||
table.notes_evaluation td.moy_ue {
|
||||
font-weight: bold;
|
||||
color:rgb(1, 116, 96);
|
||||
}
|
||||
|
||||
h2.formsemestre, .gtrcontent h2 {
|
||||
margin-top: 2px;
|
||||
font-size: 130%;
|
||||
|
@ -302,7 +302,7 @@ class ScoFake(object):
|
||||
):
|
||||
if user is None:
|
||||
user = self.default_user
|
||||
return sco_saisie_notes._notes_add(
|
||||
return sco_saisie_notes.notes_add(
|
||||
user,
|
||||
evaluation["evaluation_id"],
|
||||
[(etud["etudid"], note)],
|
||||
|
@ -3,13 +3,14 @@ Test modèles évaluations avec poids BUT
|
||||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
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.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_codes_parcours, sco_saisie_notes
|
||||
from app.scodoc.sco_utils import NOTES_ATTENTE, NOTES_NEUTRALISE
|
||||
|
||||
"""
|
||||
@ -175,28 +176,29 @@ def _setup_module_evaluation(ue_coefs=(1.0, 2.0, 3.0)):
|
||||
coefficient=0,
|
||||
)
|
||||
evaluation_id = _e1["evaluation_id"]
|
||||
return formation_id, evaluation_id, ue1, ue2, ue3
|
||||
return G, formation_id, sem, evaluation_id, ue1, ue2, ue3
|
||||
|
||||
|
||||
def test_module_conformity(test_client):
|
||||
"""Vérification coefficients module<->UE vs poids des évaluations"""
|
||||
formation_id, evaluation_id, ue1, ue2, ue3 = _setup_module_evaluation()
|
||||
_, formation_id, _, evaluation_id, ue1, ue2, ue3 = _setup_module_evaluation()
|
||||
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 = models.Evaluation.query.get(evaluation_id)
|
||||
evaluation.set_ue_poids_dict({ue1.id: p1, ue2.id: p2})
|
||||
assert evaluation.get_ue_poids_dict() == {ue1.id: p1, ue2.id: p2}
|
||||
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_ue_coefs(
|
||||
formation_id, semestre_idx
|
||||
)
|
||||
assert isinstance(modules_coefficients, pd.DataFrame)
|
||||
assert modules_coefficients.shape == (nb_ues, nb_mods)
|
||||
evals_poids = moy_mod.df_load_evaluations_poids(evaluation.moduleimpl_id)
|
||||
evals_poids, ues = moy_mod.df_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.check_moduleimpl_conformity(
|
||||
@ -204,9 +206,9 @@ def test_module_conformity(test_client):
|
||||
)
|
||||
|
||||
|
||||
def test_module_moy():
|
||||
def test_module_moy_elem(test_client):
|
||||
"""Vérification calcul moyenne d'un module
|
||||
(calcul bas niveau)
|
||||
(notes entrées dans un DataFrame sans passer par ScoDoc)
|
||||
"""
|
||||
# Repris du notebook CalculNotesBUT.ipynb
|
||||
data = [ # Les notes de chaque étudiant dans les 2 evals:
|
||||
@ -240,12 +242,71 @@ def test_module_moy():
|
||||
{"UE1": 2, "UE2": 5, "UE3": 0},
|
||||
]
|
||||
evals_poids = pd.DataFrame(data, index=["EVAL1", "EVAL2"], dtype=float)
|
||||
etud_moy_module_df = moy_mod.compute_module_moy(evals_notes, evals_poids)
|
||||
etud_moy_module_df = moy_mod.compute_module_moy(
|
||||
evals_notes.fillna(0.0), evals_poids
|
||||
)
|
||||
NAN = 666.0 # pour pouvoir comparer NaN et NaN (car NaN != NaN)
|
||||
r = etud_moy_module_df.fillna(NAN)
|
||||
tuple(r.loc["etud1"]) == (14 + 1 / 3, 16.0, NAN)
|
||||
tuple(r.loc["etud2"]) == (11 + 1 / 3, 17.0, NAN)
|
||||
tuple(r.loc["etud3"]) == (13, NAN, NAN)
|
||||
tuple(r.loc["etud4"]) == (17 + 1 / 3, 19, NAN)
|
||||
tuple(r.loc["etud5"]) == (0.0, 0.0, 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, sem, evaluation1_id, ue1, ue2, ue3 = _setup_module_evaluation()
|
||||
etud = G.create_etud(nom="test")
|
||||
G.inscrit_etudiant(sem, etud)
|
||||
etudid = etud["etudid"]
|
||||
evaluation1 = models.Evaluation.query.get(evaluation1_id)
|
||||
# Crée une deuxième évaluation dans le même moduleimpl:
|
||||
evaluation2_id = G.create_evaluation(
|
||||
moduleimpl_id=evaluation1.moduleimpl_id,
|
||||
jour="02/01/2021",
|
||||
description="evaluation 2",
|
||||
coefficient=coef_e2,
|
||||
)["evaluation_id"]
|
||||
evaluation2 = models.Evaluation.query.get(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})
|
||||
# Saisie d'une note dans chaque éval
|
||||
note1, note2 = 11.0, 12.0
|
||||
t = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, note1)])
|
||||
assert t == (1, 0, [])
|
||||
_ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, note2)])
|
||||
#
|
||||
# Vérifications
|
||||
moduleimpl_id = evaluation1.moduleimpl_id
|
||||
nb_evals = models.Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).count()
|
||||
assert nb_evals == 2
|
||||
nb_ues = 3
|
||||
# Calcul de la moyenne du module
|
||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(moduleimpl_id)
|
||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||
evals_notes, evaluations = moy_mod.df_load_modimpl_notes(moduleimpl_id)
|
||||
evals_notes_sur_20 = moy_mod.normalize_evals_notes(evals_notes, evaluations)
|
||||
etud_moy_module = moy_mod.compute_module_moy(
|
||||
evals_notes_sur_20, evals_poids, [coef_e1, coef_e2]
|
||||
)
|
||||
# Moyenne dans les UE 1, 2, 3:
|
||||
moy_ue1 = etud_moy_module[ue1.id][etudid]
|
||||
assert moy_ue1 == ((note1 * e1p1 * coef_e1) + (note2 * e2p1 * coef_e2)) / (
|
||||
e1p1 * coef_e1 + e2p1 * coef_e2
|
||||
)
|
||||
moy_ue2 = etud_moy_module[ue2.id][etudid]
|
||||
assert moy_ue2 == ((note1 * e1p2 * coef_e1) + (note2 * e2p2 * coef_e2)) / (
|
||||
e1p2 * coef_e1 + e2p2 * coef_e2
|
||||
)
|
||||
moy_ue3 = etud_moy_module[ue3.id][etudid]
|
||||
assert np.isnan(moy_ue3)
|
||||
# moy_ue3 == ((note1 * e1p3 * coef_e1) + (note2 * e2p3 * coef_e2)) / (
|
||||
# e1p3 * coef_e1 + e2p3 * coef_e2)
|
||||
|
@ -274,6 +274,10 @@ def test_notes_modules(test_client):
|
||||
{"etudid": etudid, "moduleimpl_id": mi2["moduleimpl_id"]},
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
sco_moduleimpl.do_moduleimpl_inscription_create(
|
||||
{"etudid": etuds[1]["etudid"], "moduleimpl_id": mi2["moduleimpl_id"]},
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
assert ue_status["nb_missing"] == 1 # mi2 n'a pas encore de note
|
||||
@ -288,8 +292,8 @@ def test_notes_modules(test_client):
|
||||
_, _, _ = G.create_note(evaluation=e_m2, etud=etud, note=19.5)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
assert ue_status["nb_missing"] == 0
|
||||
assert ue_status["nb_notes"] == 2
|
||||
assert ue_status["nb_missing"] == 1 # manque une note
|
||||
assert ue_status["nb_notes"] == 1
|
||||
|
||||
# Moyenne d'UE si l'un des modules est EXC ("NA")
|
||||
# 2 modules, notes EXC dans le premier, note valide n dans le second
|
||||
|
Loading…
Reference in New Issue
Block a user