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
|
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
|
"""Charge poids des évaluations d'un module et retourne un dataframe
|
||||||
rows = evaluations, columns = UE, value = poids (float).
|
rows = evaluations, columns = UE, value = poids (float).
|
||||||
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
||||||
remplies par default_poids.
|
remplies par default_poids.
|
||||||
|
Résultat: (evals_poids, liste de UE du semestre)
|
||||||
"""
|
"""
|
||||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
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
|
df[eval_poids.ue_id][eval_poids.evaluation_id] = eval_poids.poids
|
||||||
if default_poids is not None:
|
if default_poids is not None:
|
||||||
df.fillna(value=default_poids, inplace=True)
|
df.fillna(value=default_poids, inplace=True)
|
||||||
return df
|
return df, ues
|
||||||
|
|
||||||
|
|
||||||
def check_moduleimpl_conformity(
|
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)
|
colonnes: evaluation_id (le nom de la colonne est l'evaluation_id en str)
|
||||||
index (lignes): etudid
|
index (lignes): etudid
|
||||||
|
|
||||||
|
Résultat: (evals_notes, liste de évaluations du moduleimpl)
|
||||||
|
|
||||||
L'ensemble des étudiants est celui des inscrits au module.
|
L'ensemble des étudiants est celui des inscrits au module.
|
||||||
|
|
||||||
Valeurs des notes:
|
Les notes renvoyées sont "brutes" et peuvent prendre els valeurs:
|
||||||
note : float (valeur enregistrée brute, pas normalisée sur 20)
|
note : float (valeur enregistrée brute, non normalisée sur 20)
|
||||||
pas de note: NaN
|
pas de note: NaN
|
||||||
absent: 0.
|
absent: NaN
|
||||||
excusé: NOTES_NEUTRALISE (voir sco_utils)
|
excusé: NOTES_NEUTRALISE (voir sco_utils)
|
||||||
attente: NOTES_ATTENTE
|
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]
|
etudids = [e.etudid for e in ModuleImpl.query.get(moduleimpl_id).inscriptions]
|
||||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id)
|
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:
|
for evaluation in evaluations:
|
||||||
eval_df = pd.read_sql(
|
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},
|
params={"evaluation_id": evaluation.evaluation_id},
|
||||||
index_col="etudid",
|
index_col="etudid",
|
||||||
)
|
)
|
||||||
# Remplace les ABS (NULL en BD, donc NaN ici) par des zéros.
|
evals_notes = evals_notes.merge(
|
||||||
eval_df.fillna(value=0.0, inplace=True)
|
eval_df, how="outer", left_index=True, right_index=True
|
||||||
df = df.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
|
"""Calcule les moyennes des étudiants dans ce module
|
||||||
|
|
||||||
- evals_notes : DataFrame, colonnes: EVALS, Lignes: etudid
|
- 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_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||||
|
|
||||||
|
- evals_coefs: sequence, 1 coef par UE
|
||||||
|
|
||||||
Résultat: DataFrame, colonnes UE, lignes etud
|
Résultat: DataFrame, colonnes UE, lignes etud
|
||||||
= la note de l'étudiant dans chaque UE pour ce module.
|
= la note de l'étudiant dans chaque UE pour ce module.
|
||||||
ou NaN si les évaluations (dans lesquelles l'étudiant à des notes)
|
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_etuds = len(evals_notes)
|
||||||
nb_ues = evals_poids.shape[1]
|
nb_ues = evals_poids.shape[1]
|
||||||
etud_moy_module_arr = np.zeros((nb_etuds, nb_ues))
|
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()
|
evals_notes_arr = evals_notes.values # .to_numpy()
|
||||||
val_neutres = np.array((scu.NOTES_NEUTRALISE, scu.NOTES_ATTENTE))
|
val_neutres = np.array((scu.NOTES_NEUTRALISE, scu.NOTES_ATTENTE))
|
||||||
for i in range(nb_etuds):
|
for i in range(nb_etuds):
|
||||||
|
@ -46,7 +46,7 @@ class Evaluation(db.Model):
|
|||||||
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
|
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
|
||||||
|
|
||||||
def __repr__(self):
|
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):
|
def to_dict(self):
|
||||||
e = dict(self.__dict__)
|
e = dict(self.__dict__)
|
||||||
|
@ -5,6 +5,7 @@ from typing import Any
|
|||||||
from app import db
|
from app import db
|
||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.models import SHORT_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 import sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
@ -86,6 +87,16 @@ class UniteEns(db.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}(id={self.id}, formation_id={self.formation_id}, acronyme='{self.acronyme}')>"
|
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):
|
def is_locked(self):
|
||||||
"""True if UE should not be modified
|
"""True if UE should not be modified
|
||||||
(contains modules used in a locked formsemestre)
|
(contains modules used in a locked formsemestre)
|
||||||
@ -95,6 +106,20 @@ class UniteEns(db.Model):
|
|||||||
|
|
||||||
return sco_edit_ue.ue_is_locked(self.id)
|
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):
|
class Matiere(db.Model):
|
||||||
"""Matières: regroupe les modules d'une UE
|
"""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
|
# force module semestre_idx to its UE
|
||||||
|
if a_module.ue.semestre_idx:
|
||||||
module["semestre_id"] = 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(
|
tf = TrivialFormulator(
|
||||||
request.base_url,
|
request.base_url,
|
||||||
scu.get_request_args(),
|
scu.get_request_args(),
|
||||||
|
@ -467,8 +467,19 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
|||||||
else:
|
else:
|
||||||
semestre_idx = int(semestre_idx)
|
semestre_idx = int(semestre_idx)
|
||||||
semestre_ids = range(1, parcours.NB_SEM + 1)
|
semestre_ids = range(1, parcours.NB_SEM + 1)
|
||||||
ues = ue_list(args={"formation_id": formation_id, "is_external": False})
|
# transition: on requete ici via l'ORM mais on utilise les fonctions ScoDoc7
|
||||||
ues_externes = ue_list(args={"formation_id": formation_id, "is_external": True})
|
# 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:
|
# tri par semestre et numero:
|
||||||
_add_ue_semestre_id(ues, is_apc)
|
_add_ue_semestre_id(ues, is_apc)
|
||||||
_add_ue_semestre_id(ues_externes, is_apc)
|
_add_ue_semestre_id(ues_externes, is_apc)
|
||||||
@ -928,78 +939,6 @@ def _ue_table_matieres(
|
|||||||
return "\n".join(H)
|
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(
|
def _ue_table_modules(
|
||||||
parcours,
|
parcours,
|
||||||
mat,
|
mat,
|
||||||
|
@ -31,9 +31,11 @@
|
|||||||
import flask
|
import flask
|
||||||
from flask import url_for, g, request
|
from flask import url_for, g, request
|
||||||
|
|
||||||
|
from app import models
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.comp import moy_mod
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
@ -204,6 +206,7 @@ def do_evaluation_listenotes():
|
|||||||
group_ids=tf[2]["group_ids"],
|
group_ids=tf[2]["group_ids"],
|
||||||
hide_groups=hide_groups,
|
hide_groups=hide_groups,
|
||||||
with_emails=with_emails,
|
with_emails=with_emails,
|
||||||
|
mode=mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -216,15 +219,22 @@ def _make_table_notes(
|
|||||||
hide_groups=False,
|
hide_groups=False,
|
||||||
with_emails=False,
|
with_emails=False,
|
||||||
group_ids=[],
|
group_ids=[],
|
||||||
|
mode="module", # "eval" or "module"
|
||||||
):
|
):
|
||||||
"""Table liste notes (une seule évaluation ou toutes celles d'un module)"""
|
"""Table liste notes (une seule évaluation ou toutes celles d'un module)"""
|
||||||
|
# Code à ré-écrire !
|
||||||
if not evals:
|
if not evals:
|
||||||
return "<p>Aucune évaluation !</p>"
|
return "<p>Aucune évaluation !</p>"
|
||||||
E = evals[0]
|
E = evals[0]
|
||||||
moduleimpl_id = E["moduleimpl_id"]
|
moduleimpl_id = E["moduleimpl_id"]
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
module = models.Module.query.get(modimpl["module_id"])
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_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:
|
# (debug) check that all evals are in same module:
|
||||||
for e in evals:
|
for e in evals:
|
||||||
if e["moduleimpl_id"] != moduleimpl_id:
|
if e["moduleimpl_id"] != moduleimpl_id:
|
||||||
@ -236,16 +246,12 @@ def _make_table_notes(
|
|||||||
keep_numeric = False
|
keep_numeric = False
|
||||||
# Si pas de groupe, affiche tout
|
# Si pas de groupe, affiche tout
|
||||||
if not group_ids:
|
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)
|
groups = sco_groups.listgroups(group_ids)
|
||||||
|
|
||||||
gr_title = sco_groups.listgroups_abbrev(groups)
|
gr_title = sco_groups.listgroups_abbrev(groups)
|
||||||
gr_title_filename = sco_groups.listgroups_filename(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:
|
if anonymous_listing:
|
||||||
columns_ids = ["code"] # cols in table
|
columns_ids = ["code"] # cols in table
|
||||||
else:
|
else:
|
||||||
@ -269,7 +275,7 @@ def _make_table_notes(
|
|||||||
|
|
||||||
rows = []
|
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):
|
def __init__(self):
|
||||||
self.lastkey = 1
|
self.lastkey = 1
|
||||||
|
|
||||||
@ -279,7 +285,19 @@ def _make_table_notes(
|
|||||||
# self.lastkey = chr(ord(self.lastkey)+1)
|
# self.lastkey = chr(ord(self.lastkey)+1)
|
||||||
return str(r)
|
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:
|
for etudid, etat in etudid_etats:
|
||||||
css_row_class = None
|
css_row_class = None
|
||||||
# infos identite etudiant
|
# infos identite etudiant
|
||||||
@ -295,11 +313,7 @@ def _make_table_notes(
|
|||||||
else:
|
else:
|
||||||
grc = etat
|
grc = etat
|
||||||
|
|
||||||
code = "" # code pour listings anonyme, à la place du nom
|
code = etud.get(anonymous_lst_key)
|
||||||
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"]
|
|
||||||
if not code: # laisser le code vide n'aurait aucun sens, prenons l'etudid
|
if not code: # laisser le code vide n'aurait aucun sens, prenons l'etudid
|
||||||
code = etudid
|
code = etudid
|
||||||
|
|
||||||
@ -310,7 +324,7 @@ def _make_table_notes(
|
|||||||
"etudid": etudid,
|
"etudid": etudid,
|
||||||
"nom": etud["nom"].upper(),
|
"nom": etud["nom"].upper(),
|
||||||
"_nomprenom_target": "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s"
|
"_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"]),
|
"_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]),
|
||||||
"prenom": etud["prenom"].lower().capitalize(),
|
"prenom": etud["prenom"].lower().capitalize(),
|
||||||
"nomprenom": etud["nomprenom"],
|
"nomprenom": etud["nomprenom"],
|
||||||
@ -322,7 +336,7 @@ def _make_table_notes(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Lignes en tête:
|
# Lignes en tête:
|
||||||
coefs = {
|
row_coefs = {
|
||||||
"nom": "",
|
"nom": "",
|
||||||
"prenom": "",
|
"prenom": "",
|
||||||
"nomprenom": "",
|
"nomprenom": "",
|
||||||
@ -331,7 +345,16 @@ def _make_table_notes(
|
|||||||
"_css_row_class": "sorttop fontitalic",
|
"_css_row_class": "sorttop fontitalic",
|
||||||
"_table_part": "head",
|
"_table_part": "head",
|
||||||
}
|
}
|
||||||
note_max = {
|
row_poids = {
|
||||||
|
"nom": "",
|
||||||
|
"prenom": "",
|
||||||
|
"nomprenom": "",
|
||||||
|
"group": "",
|
||||||
|
"code": "",
|
||||||
|
"_css_row_class": "sorttop poids",
|
||||||
|
"_table_part": "head",
|
||||||
|
}
|
||||||
|
row_note_max = {
|
||||||
"nom": "",
|
"nom": "",
|
||||||
"prenom": "",
|
"prenom": "",
|
||||||
"nomprenom": "",
|
"nomprenom": "",
|
||||||
@ -340,7 +363,7 @@ def _make_table_notes(
|
|||||||
"_css_row_class": "sorttop fontitalic",
|
"_css_row_class": "sorttop fontitalic",
|
||||||
"_table_part": "head",
|
"_table_part": "head",
|
||||||
}
|
}
|
||||||
moys = {
|
row_moys = {
|
||||||
"_css_row_class": "moyenne sortbottom",
|
"_css_row_class": "moyenne sortbottom",
|
||||||
"_table_part": "foot",
|
"_table_part": "foot",
|
||||||
#'_nomprenom_td_attrs' : 'colspan="2" ',
|
#'_nomprenom_td_attrs' : 'colspan="2" ',
|
||||||
@ -352,12 +375,16 @@ def _make_table_notes(
|
|||||||
e["eval_state"] = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
e["eval_state"] = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
||||||
notes, nb_abs, nb_att = _add_eval_columns(
|
notes, nb_abs, nb_att = _add_eval_columns(
|
||||||
e,
|
e,
|
||||||
|
evals_poids,
|
||||||
|
ues,
|
||||||
rows,
|
rows,
|
||||||
titles,
|
titles,
|
||||||
coefs,
|
row_coefs,
|
||||||
note_max,
|
row_poids,
|
||||||
moys,
|
row_note_max,
|
||||||
K,
|
row_moys,
|
||||||
|
is_apc,
|
||||||
|
key_mgr,
|
||||||
note_sur_20,
|
note_sur_20,
|
||||||
keep_numeric,
|
keep_numeric,
|
||||||
)
|
)
|
||||||
@ -370,28 +397,51 @@ def _make_table_notes(
|
|||||||
key=lambda x: (x["nom"] or "", x["prenom"] or "")
|
key=lambda x: (x["nom"] or "", x["prenom"] or "")
|
||||||
) # sort by nom, prenom
|
) # sort by nom, prenom
|
||||||
|
|
||||||
# Si module, ajoute moyenne du module:
|
# Si module, ajoute la (les) "moyenne(s) du module:
|
||||||
|
if mode == "module":
|
||||||
if len(evals) > 1:
|
if len(evals) > 1:
|
||||||
|
# Moyenne de l'étudant dans le module
|
||||||
|
# Affichée même en APC à titre indicatif
|
||||||
_add_moymod_column(
|
_add_moymod_column(
|
||||||
sem["formsemestre_id"],
|
sem["formsemestre_id"],
|
||||||
e,
|
moduleimpl_id,
|
||||||
rows,
|
rows,
|
||||||
|
columns_ids,
|
||||||
titles,
|
titles,
|
||||||
coefs,
|
row_coefs,
|
||||||
note_max,
|
row_poids,
|
||||||
moys,
|
row_note_max,
|
||||||
note_sur_20,
|
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,
|
keep_numeric,
|
||||||
)
|
)
|
||||||
columns_ids.append("moymod")
|
|
||||||
|
|
||||||
# Ajoute colonnes emails tout à droite:
|
# Ajoute colonnes emails tout à droite:
|
||||||
if with_emails:
|
if with_emails:
|
||||||
columns_ids += ["email", "emailperso"]
|
columns_ids += ["email", "emailperso"]
|
||||||
# Ajoute lignes en tête et moyennes
|
# Ajoute lignes en tête et moyennes
|
||||||
if len(evals) > 0:
|
if len(evals) > 0:
|
||||||
rows = [coefs, note_max] + rows
|
rows_head = [row_coefs]
|
||||||
rows.append(moys)
|
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:
|
# ajout liens HTMl vers affichage une evaluation:
|
||||||
if format == "html" and len(evals) > 1:
|
if format == "html" and len(evals) > 1:
|
||||||
rlinks = {"_table_part": "head"}
|
rlinks = {"_table_part": "head"}
|
||||||
@ -425,7 +475,7 @@ def _make_table_notes(
|
|||||||
if with_emails:
|
if with_emails:
|
||||||
gl = "&with_emails%3Alist=yes" + gl
|
gl = "&with_emails%3Alist=yes" + gl
|
||||||
if len(evals) == 1:
|
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))
|
hh = "%s, %s (%d étudiants)" % (E["description"], gr_title, len(etudid_etats))
|
||||||
filename = scu.make_filename("notes_%s_%s" % (evalname, gr_title_filename))
|
filename = scu.make_filename("notes_%s_%s" % (evalname, gr_title_filename))
|
||||||
caption = hh
|
caption = hh
|
||||||
@ -437,8 +487,8 @@ def _make_table_notes(
|
|||||||
% (nb_abs, nb_att)
|
% (nb_abs, nb_att)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
filename = scu.make_filename("notes_%s_%s" % (Mod["code"], gr_title_filename))
|
filename = scu.make_filename("notes_%s_%s" % (module.code, gr_title_filename))
|
||||||
title = "Notes du module %(code)s %(titre)s" % Mod
|
title = f"Notes {module.type_name()} {module.code} {module.titre}"
|
||||||
title += " semestre %(titremois)s" % sem
|
title += " semestre %(titremois)s" % sem
|
||||||
if gr_title and gr_title != "tous":
|
if gr_title and gr_title != "tous":
|
||||||
title += " %s" % gr_title
|
title += " %s" % gr_title
|
||||||
@ -447,10 +497,11 @@ def _make_table_notes(
|
|||||||
if format == "pdf":
|
if format == "pdf":
|
||||||
caption = "" # same as pdf_title
|
caption = "" # same as pdf_title
|
||||||
pdf_title = title
|
pdf_title = title
|
||||||
html_title = (
|
html_title = f"""<h2 class="formsemestre">Notes {module.type_name()} <a href="{
|
||||||
"""<h2 class="formsemestre">Notes du module <a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a></h2>"""
|
url_for("notes.moduleimpl_status",
|
||||||
% (moduleimpl_id, Mod["code"], Mod["titre"])
|
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
|
base_url = "evaluation_listenotes?moduleimpl_id=%s" % moduleimpl_id + gl
|
||||||
# display
|
# display
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
@ -469,7 +520,7 @@ def _make_table_notes(
|
|||||||
html_title=html_title,
|
html_title=html_title,
|
||||||
pdf_title=pdf_title,
|
pdf_title=pdf_title,
|
||||||
html_class="table_leftalign notes_evaluation",
|
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
|
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -497,7 +548,7 @@ def _make_table_notes(
|
|||||||
+ "</div></td>\n",
|
+ "</div></td>\n",
|
||||||
'<td style="padding-left: 50px; vertical-align: top;"><p>',
|
'<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]))
|
commentkeys.sort(key=lambda x: int(x[1]))
|
||||||
for (comment, key) in commentkeys:
|
for (comment, key) in commentkeys:
|
||||||
C.append(
|
C.append(
|
||||||
@ -526,7 +577,19 @@ def _make_table_notes(
|
|||||||
|
|
||||||
|
|
||||||
def _add_eval_columns(
|
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"""
|
"""Add eval e"""
|
||||||
nb_notes = 0
|
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:
|
if note_sur_20:
|
||||||
nmax = 20.0
|
nmax = 20.0
|
||||||
else:
|
else:
|
||||||
nmax = e["note_max"]
|
nmax = e["note_max"]
|
||||||
if keep_numeric:
|
if keep_numeric:
|
||||||
note_max[evaluation_id] = nmax
|
row_note_max[evaluation_id] = nmax
|
||||||
else:
|
else:
|
||||||
note_max[evaluation_id] = "/ %s" % nmax
|
row_note_max[evaluation_id] = "/ %s" % nmax
|
||||||
|
|
||||||
if nb_notes > 0:
|
if nb_notes > 0:
|
||||||
moys[evaluation_id] = "%.3g" % (sum_notes / nb_notes)
|
row_moys[evaluation_id] = "%.3g" % (sum_notes / nb_notes)
|
||||||
moys[
|
row_moys[
|
||||||
"_" + str(evaluation_id) + "_help"
|
"_" + str(evaluation_id) + "_help"
|
||||||
] = "moyenne sur %d notes (%s le %s)" % (
|
] = "moyenne sur %d notes (%s le %s)" % (
|
||||||
nb_notes,
|
nb_notes,
|
||||||
@ -614,7 +681,7 @@ def _add_eval_columns(
|
|||||||
e["jour"],
|
e["jour"],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
moys[evaluation_id] = ""
|
row_moys[evaluation_id] = ""
|
||||||
|
|
||||||
titles[evaluation_id] = "%(description)s (%(jour)s)" % e
|
titles[evaluation_id] = "%(description)s (%(jour)s)" % e
|
||||||
|
|
||||||
@ -628,15 +695,29 @@ def _add_eval_columns(
|
|||||||
return notes, nb_abs, nb_att # pour histogramme
|
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(
|
def _add_moymod_column(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
e,
|
moduleimpl_id,
|
||||||
rows,
|
rows,
|
||||||
|
columns_ids,
|
||||||
titles,
|
titles,
|
||||||
coefs,
|
row_coefs,
|
||||||
note_max,
|
row_poids,
|
||||||
moys,
|
row_note_max,
|
||||||
note_sur_20,
|
row_moys,
|
||||||
|
is_apc,
|
||||||
keep_numeric,
|
keep_numeric,
|
||||||
):
|
):
|
||||||
"""Ajoute la colonne moymod à rows"""
|
"""Ajoute la colonne moymod à rows"""
|
||||||
@ -647,23 +728,61 @@ def _add_moymod_column(
|
|||||||
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
|
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
|
||||||
for row in rows:
|
for row in rows:
|
||||||
etudid = row["etudid"]
|
etudid = row["etudid"]
|
||||||
val = nt.get_etud_mod_moy(
|
val = nt.get_etud_mod_moy(moduleimpl_id, etudid) # note sur 20, ou 'NA','NI'
|
||||||
e["moduleimpl_id"], etudid
|
|
||||||
) # note sur 20, ou 'NA','NI'
|
|
||||||
row[col_id] = scu.fmt_note(val, keep_numeric=keep_numeric)
|
row[col_id] = scu.fmt_note(val, keep_numeric=keep_numeric)
|
||||||
row["_" + col_id + "_td_attrs"] = ' class="moyenne" '
|
row["_" + col_id + "_td_attrs"] = ' class="moyenne" '
|
||||||
if not isinstance(val, str):
|
if not isinstance(val, str):
|
||||||
notes.append(val)
|
notes.append(val)
|
||||||
nb_notes = nb_notes + 1
|
nb_notes = nb_notes + 1
|
||||||
sum_notes += val
|
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:
|
if keep_numeric:
|
||||||
note_max[col_id] = 20.0
|
row_note_max[col_id] = 20.0
|
||||||
else:
|
else:
|
||||||
note_max[col_id] = "/ 20"
|
row_note_max[col_id] = "/ 20"
|
||||||
titles[col_id] = "Moyenne module"
|
titles[col_id] = "Moyenne module"
|
||||||
|
columns_ids.append(col_id)
|
||||||
if nb_notes > 0:
|
if nb_notes > 0:
|
||||||
moys[col_id] = "%.3g" % (sum_notes / nb_notes)
|
row_moys[col_id] = "%.3g" % (sum_notes / nb_notes)
|
||||||
moys["_" + col_id + "_help"] = "moyenne des moyennes"
|
row_moys["_" + col_id + "_help"] = "moyenne des moyennes"
|
||||||
else:
|
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))
|
diag.append("Notes invalides pour: " + ", ".join(etudsnames))
|
||||||
raise InvalidNoteValue()
|
raise InvalidNoteValue()
|
||||||
else:
|
else:
|
||||||
nb_changed, nb_suppress, existing_decisions = _notes_add(
|
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||||
authuser, evaluation_id, L, comment
|
authuser, evaluation_id, L, comment
|
||||||
)
|
)
|
||||||
# news
|
# news
|
||||||
@ -345,7 +345,7 @@ def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
|
|||||||
)
|
)
|
||||||
# ok
|
# ok
|
||||||
comment = "Initialisation notes manquantes"
|
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
|
# news
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
mod = sco_edit_module.module_list(args={"module_id": M["module_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()]
|
notes = [(etudid, scu.NOTES_SUPPRESS) for etudid in NotesDB.keys()]
|
||||||
|
|
||||||
if not dialog_confirmed:
|
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
|
current_user, evaluation_id, notes, do_it=False
|
||||||
)
|
)
|
||||||
msg = (
|
msg = (
|
||||||
@ -425,7 +425,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# modif
|
# 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"
|
current_user, evaluation_id, notes, comment="effacer tout"
|
||||||
)
|
)
|
||||||
assert nb_changed == nb_suppress
|
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()
|
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
|
Insert or update notes
|
||||||
notes is a list of tuples (etudid,value)
|
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.
|
WOULD be changed or suppressed.
|
||||||
Nota:
|
Nota:
|
||||||
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
|
- 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(
|
now = psycopg2.Timestamp(
|
||||||
*time.localtime()[:6]
|
*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
|
else: # suppression ancienne note
|
||||||
if do_it:
|
if do_it:
|
||||||
log(
|
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)
|
% (evaluation_id, etudid, oldval)
|
||||||
)
|
)
|
||||||
cursor.execute(
|
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):
|
if has_existing_decision(M, E, etudid):
|
||||||
existing_decisions.append(etudid)
|
existing_decisions.append(etudid)
|
||||||
except:
|
except:
|
||||||
log("*** exception in _notes_add")
|
log("*** exception in notes_add")
|
||||||
if do_it:
|
if do_it:
|
||||||
cnx.rollback() # abort
|
cnx.rollback() # abort
|
||||||
# inval cache
|
# inval cache
|
||||||
@ -1265,7 +1265,7 @@ def save_note(etudid=None, evaluation_id=None, value=None, comment=""):
|
|||||||
else:
|
else:
|
||||||
L, _, _, _, _ = _check_notes([(etudid, value)], E, Mod)
|
L, _, _, _, _ = _check_notes([(etudid, value)], E, Mod)
|
||||||
if L:
|
if L:
|
||||||
nbchanged, _, existing_decisions = _notes_add(
|
nbchanged, _, existing_decisions = notes_add(
|
||||||
authuser, evaluation_id, L, comment=comment, do_it=True
|
authuser, evaluation_id, L, comment=comment, do_it=True
|
||||||
)
|
)
|
||||||
sco_news.add(
|
sco_news.add(
|
||||||
|
@ -167,7 +167,7 @@ def external_ue_inscrit_et_note(moduleimpl_id, formsemestre_id, notes_etuds):
|
|||||||
description="note externe",
|
description="note externe",
|
||||||
)
|
)
|
||||||
# Saisie des notes
|
# Saisie des notes
|
||||||
_, _, _ = sco_saisie_notes._notes_add(
|
_, _, _ = sco_saisie_notes.notes_add(
|
||||||
current_user,
|
current_user,
|
||||||
evaluation_id,
|
evaluation_id,
|
||||||
list(notes_etuds.items()),
|
list(notes_etuds.items()),
|
||||||
|
@ -44,6 +44,7 @@ import unicodedata
|
|||||||
import urllib
|
import urllib
|
||||||
from urllib.parse import urlparse, parse_qsl, urlunparse, urlencode
|
from urllib.parse import urlparse, parse_qsl, urlunparse, urlencode
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
import pydot
|
import pydot
|
||||||
import requests
|
import requests
|
||||||
@ -138,6 +139,8 @@ def fmt_note(val, note_max=None, keep_numeric=False):
|
|||||||
if val == NOTES_ATTENTE:
|
if val == NOTES_ATTENTE:
|
||||||
return "ATT" # attente, note neutralisee
|
return "ATT" # attente, note neutralisee
|
||||||
if isinstance(val, float) or isinstance(val, int):
|
if isinstance(val, float) or isinstance(val, int):
|
||||||
|
if np.isnan(val):
|
||||||
|
return "/"
|
||||||
if note_max != None and note_max > 0:
|
if note_max != None and note_max > 0:
|
||||||
val = val * 20.0 / note_max
|
val = val * 20.0 / note_max
|
||||||
if keep_numeric:
|
if keep_numeric:
|
||||||
|
@ -1040,6 +1040,14 @@ td.colcomment, span.colcomment {
|
|||||||
color: rgb(80,100,80);
|
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 {
|
h2.formsemestre, .gtrcontent h2 {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
font-size: 130%;
|
font-size: 130%;
|
||||||
|
@ -302,7 +302,7 @@ class ScoFake(object):
|
|||||||
):
|
):
|
||||||
if user is None:
|
if user is None:
|
||||||
user = self.default_user
|
user = self.default_user
|
||||||
return sco_saisie_notes._notes_add(
|
return sco_saisie_notes.notes_add(
|
||||||
user,
|
user,
|
||||||
evaluation["evaluation_id"],
|
evaluation["evaluation_id"],
|
||||||
[(etud["etudid"], note)],
|
[(etud["etudid"], note)],
|
||||||
|
@ -3,13 +3,14 @@ Test modèles évaluations avec poids BUT
|
|||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from app.models.etudiants import Identite
|
||||||
|
|
||||||
from tests.unit import sco_fake_gen
|
from tests.unit import sco_fake_gen
|
||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import models
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
from app.comp import moy_ue
|
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
|
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,
|
coefficient=0,
|
||||||
)
|
)
|
||||||
evaluation_id = _e1["evaluation_id"]
|
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):
|
def test_module_conformity(test_client):
|
||||||
"""Vérification coefficients module<->UE vs poids des évaluations"""
|
"""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
|
semestre_idx = 2
|
||||||
nb_ues = 3 # 3 UEs dans ce test
|
nb_ues = 3 # 3 UEs dans ce test
|
||||||
nb_mods = 1 # 1 seul module
|
nb_mods = 1 # 1 seul module
|
||||||
nb_evals = 1 # 1 seule evaluation pour l'instant
|
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
|
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 = models.Evaluation.query.get(evaluation_id)
|
||||||
evaluation.set_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}
|
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
|
# 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(
|
modules_coefficients, _ues, _modules = moy_ue.df_load_ue_coefs(
|
||||||
formation_id, semestre_idx
|
formation_id, semestre_idx
|
||||||
)
|
)
|
||||||
assert isinstance(modules_coefficients, pd.DataFrame)
|
assert isinstance(modules_coefficients, pd.DataFrame)
|
||||||
assert modules_coefficients.shape == (nb_ues, nb_mods)
|
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 isinstance(evals_poids, pd.DataFrame)
|
||||||
|
assert len(ues) == nb_ues
|
||||||
assert all(evals_poids.dtypes == np.float64)
|
assert all(evals_poids.dtypes == np.float64)
|
||||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||||
assert not moy_mod.check_moduleimpl_conformity(
|
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
|
"""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
|
# Repris du notebook CalculNotesBUT.ipynb
|
||||||
data = [ # Les notes de chaque étudiant dans les 2 evals:
|
data = [ # Les notes de chaque étudiant dans les 2 evals:
|
||||||
@ -240,12 +242,71 @@ def test_module_moy():
|
|||||||
{"UE1": 2, "UE2": 5, "UE3": 0},
|
{"UE1": 2, "UE2": 5, "UE3": 0},
|
||||||
]
|
]
|
||||||
evals_poids = pd.DataFrame(data, index=["EVAL1", "EVAL2"], dtype=float)
|
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)
|
NAN = 666.0 # pour pouvoir comparer NaN et NaN (car NaN != NaN)
|
||||||
r = etud_moy_module_df.fillna(NAN)
|
r = etud_moy_module_df.fillna(NAN)
|
||||||
tuple(r.loc["etud1"]) == (14 + 1 / 3, 16.0, NAN)
|
assert tuple(r.loc["etud1"]) == (14 + 1 / 3, 16.0, NAN)
|
||||||
tuple(r.loc["etud2"]) == (11 + 1 / 3, 17.0, NAN)
|
assert tuple(r.loc["etud2"]) == (11 + 1 / 3, 17.0, NAN)
|
||||||
tuple(r.loc["etud3"]) == (13, NAN, NAN)
|
assert tuple(r.loc["etud3"]) == (13, NAN, NAN)
|
||||||
tuple(r.loc["etud4"]) == (17 + 1 / 3, 19, NAN)
|
assert tuple(r.loc["etud4"]) == (17 + 1 / 3, 19, NAN)
|
||||||
tuple(r.loc["etud5"]) == (0.0, 0.0, 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
|
# 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"]},
|
{"etudid": etudid, "moduleimpl_id": mi2["moduleimpl_id"]},
|
||||||
formsemestre_id=formsemestre_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)
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
assert ue_status["nb_missing"] == 1 # mi2 n'a pas encore de note
|
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)
|
_, _, _ = G.create_note(evaluation=e_m2, etud=etud, note=19.5)
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
assert ue_status["nb_missing"] == 0
|
assert ue_status["nb_missing"] == 1 # manque une note
|
||||||
assert ue_status["nb_notes"] == 2
|
assert ue_status["nb_notes"] == 1
|
||||||
|
|
||||||
# Moyenne d'UE si l'un des modules est EXC ("NA")
|
# Moyenne d'UE si l'un des modules est EXC ("NA")
|
||||||
# 2 modules, notes EXC dans le premier, note valide n dans le second
|
# 2 modules, notes EXC dans le premier, note valide n dans le second
|
||||||
|
Loading…
Reference in New Issue
Block a user