forked from ScoDoc/ScoDoc
Evaluations de type bonus. Implements #848
This commit is contained in:
parent
81fab97018
commit
7f32f1fb99
@ -104,9 +104,11 @@ class BulletinBUT:
|
||||
"competence": None, # XXX TODO lien avec référentiel
|
||||
"moyenne": None,
|
||||
# Le bonus sport appliqué sur cette UE
|
||||
"bonus": fmt_note(res.bonus_ues[ue.id][etud.id])
|
||||
if res.bonus_ues is not None and ue.id in res.bonus_ues
|
||||
else fmt_note(0.0),
|
||||
"bonus": (
|
||||
fmt_note(res.bonus_ues[ue.id][etud.id])
|
||||
if res.bonus_ues is not None and ue.id in res.bonus_ues
|
||||
else fmt_note(0.0)
|
||||
),
|
||||
"malus": fmt_note(res.malus[ue.id][etud.id]),
|
||||
"capitalise": None, # "AAAA-MM-JJ" TODO #sco93
|
||||
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
|
||||
@ -181,14 +183,16 @@ class BulletinBUT:
|
||||
"is_external": ue_capitalisee.is_external,
|
||||
"date_capitalisation": ue_capitalisee.event_date,
|
||||
"formsemestre_id": ue_capitalisee.formsemestre_id,
|
||||
"bul_orig_url": url_for(
|
||||
"notes.formsemestre_bulletinetud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud.id,
|
||||
formsemestre_id=ue_capitalisee.formsemestre_id,
|
||||
)
|
||||
if ue_capitalisee.formsemestre_id
|
||||
else None,
|
||||
"bul_orig_url": (
|
||||
url_for(
|
||||
"notes.formsemestre_bulletinetud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud.id,
|
||||
formsemestre_id=ue_capitalisee.formsemestre_id,
|
||||
)
|
||||
if ue_capitalisee.formsemestre_id
|
||||
else None
|
||||
),
|
||||
"ressources": {}, # sans détail en BUT
|
||||
"saes": {},
|
||||
}
|
||||
@ -227,13 +231,15 @@ class BulletinBUT:
|
||||
"id": modimpl.id,
|
||||
"titre": modimpl.module.titre,
|
||||
"code_apogee": modimpl.module.code_apogee,
|
||||
"url": url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=modimpl.id,
|
||||
)
|
||||
if has_request_context()
|
||||
else "na",
|
||||
"url": (
|
||||
url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=modimpl.id,
|
||||
)
|
||||
if has_request_context()
|
||||
else "na"
|
||||
),
|
||||
"moyenne": {
|
||||
# # moyenne indicative de module: moyenne des UE,
|
||||
# # ignorant celles sans notes (nan)
|
||||
@ -242,18 +248,20 @@ class BulletinBUT:
|
||||
# "max": fmt_note(moyennes_etuds.max()),
|
||||
# "moy": fmt_note(moyennes_etuds.mean()),
|
||||
},
|
||||
"evaluations": [
|
||||
self.etud_eval_results(etud, e)
|
||||
for e in modimpl.evaluations
|
||||
if (e.visibulletin or version == "long")
|
||||
and (e.id in modimpl_results.evaluations_etat)
|
||||
and (
|
||||
modimpl_results.evaluations_etat[e.id].is_complete
|
||||
or self.prefs["bul_show_all_evals"]
|
||||
)
|
||||
]
|
||||
if version != "short"
|
||||
else [],
|
||||
"evaluations": (
|
||||
[
|
||||
self.etud_eval_results(etud, e)
|
||||
for e in modimpl.evaluations
|
||||
if (e.visibulletin or version == "long")
|
||||
and (e.id in modimpl_results.evaluations_etat)
|
||||
and (
|
||||
modimpl_results.evaluations_etat[e.id].is_complete
|
||||
or self.prefs["bul_show_all_evals"]
|
||||
)
|
||||
]
|
||||
if version != "short"
|
||||
else []
|
||||
),
|
||||
}
|
||||
return d
|
||||
|
||||
@ -274,9 +282,11 @@ class BulletinBUT:
|
||||
poids = collections.defaultdict(lambda: 0.0)
|
||||
d = {
|
||||
"id": e.id,
|
||||
"coef": fmt_note(e.coefficient)
|
||||
if e.evaluation_type == scu.EVALUATION_NORMALE
|
||||
else None,
|
||||
"coef": (
|
||||
fmt_note(e.coefficient)
|
||||
if e.evaluation_type == Evaluation.EVALUATION_NORMALE
|
||||
else None
|
||||
),
|
||||
"date_debut": e.date_debut.isoformat() if e.date_debut else None,
|
||||
"date_fin": e.date_fin.isoformat() if e.date_fin else None,
|
||||
"description": e.description,
|
||||
@ -291,18 +301,20 @@ class BulletinBUT:
|
||||
"moy": fmt_note(notes_ok.mean(), note_max=e.note_max),
|
||||
},
|
||||
"poids": poids,
|
||||
"url": url_for(
|
||||
"notes.evaluation_listenotes",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
evaluation_id=e.id,
|
||||
)
|
||||
if has_request_context()
|
||||
else "na",
|
||||
"url": (
|
||||
url_for(
|
||||
"notes.evaluation_listenotes",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
evaluation_id=e.id,
|
||||
)
|
||||
if has_request_context()
|
||||
else "na"
|
||||
),
|
||||
# deprecated (supprimer avant #sco9.7)
|
||||
"date": e.date_debut.isoformat() if e.date_debut else None,
|
||||
"heure_debut": e.date_debut.time().isoformat("minutes")
|
||||
if e.date_debut
|
||||
else None,
|
||||
"heure_debut": (
|
||||
e.date_debut.time().isoformat("minutes") if e.date_debut else None
|
||||
),
|
||||
"heure_fin": e.date_fin.time().isoformat("minutes") if e.date_fin else None,
|
||||
}
|
||||
return d
|
||||
@ -524,9 +536,9 @@ class BulletinBUT:
|
||||
|
||||
d.update(infos)
|
||||
# --- Rangs
|
||||
d[
|
||||
"rang_nt"
|
||||
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||
d["rang_nt"] = (
|
||||
f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||
)
|
||||
d["rang_txt"] = "Rang " + d["rang_nt"]
|
||||
|
||||
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
|
||||
|
@ -24,7 +24,7 @@ from reportlab.lib.colors import blue
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.platypus import Paragraph, Spacer
|
||||
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models import Evaluation, ScoDocSiteConfig
|
||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||
from app.scodoc import gen_tables
|
||||
from app.scodoc.codes_cursus import UE_SPORT
|
||||
@ -422,7 +422,11 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||
def evaluations_rows(self, rows, evaluations: list[dict], ue_acros=()):
|
||||
"lignes des évaluations"
|
||||
for e in evaluations:
|
||||
coef = e["coef"] if e["evaluation_type"] == scu.EVALUATION_NORMALE else "*"
|
||||
coef = (
|
||||
e["coef"]
|
||||
if e["evaluation_type"] == Evaluation.EVALUATION_NORMALE
|
||||
else "*"
|
||||
)
|
||||
t = {
|
||||
"titre": f"{e['description'] or ''}",
|
||||
"moyenne": e["note"]["value"],
|
||||
@ -431,7 +435,10 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||
),
|
||||
"coef": coef,
|
||||
"_coef_pdf": Paragraph(
|
||||
f"<para align=right fontSize={self.small_fontsize}><i>{coef}</i></para>"
|
||||
f"""<para align=right fontSize={self.small_fontsize}><i>{
|
||||
coef if e["evaluation_type"] != Evaluation.EVALUATION_BONUS
|
||||
else "bonus"
|
||||
}</i></para>"""
|
||||
),
|
||||
"_pdf_style": [
|
||||
(
|
||||
|
@ -157,8 +157,7 @@ class ModuleImplResults:
|
||||
|
||||
etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem.
|
||||
is_complete = (
|
||||
(evaluation.evaluation_type == scu.EVALUATION_RATTRAPAGE)
|
||||
or (evaluation.evaluation_type == scu.EVALUATION_SESSION2)
|
||||
(evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE)
|
||||
or (evaluation.publish_incomplete)
|
||||
or (not etudids_sans_note)
|
||||
)
|
||||
@ -240,19 +239,20 @@ class ModuleImplResults:
|
||||
).formsemestre.inscriptions
|
||||
]
|
||||
|
||||
def get_evaluations_coefs(self, moduleimpl: ModuleImpl) -> np.array:
|
||||
def get_evaluations_coefs(self, modimpl: ModuleImpl) -> np.array:
|
||||
"""Coefficients des évaluations.
|
||||
Les coefs des évals incomplètes et non "normales" (session 2, rattrapage)
|
||||
sont zéro.
|
||||
Les coefs des évals incomplètes, rattrapage, session 2, bonus sont forcés à zéro.
|
||||
Résultat: 2d-array of floats, shape (nb_evals, 1)
|
||||
"""
|
||||
return (
|
||||
np.array(
|
||||
[
|
||||
e.coefficient
|
||||
if e.evaluation_type == scu.EVALUATION_NORMALE
|
||||
else 0.0
|
||||
for e in moduleimpl.evaluations
|
||||
(
|
||||
e.coefficient
|
||||
if e.evaluation_type == Evaluation.EVALUATION_NORMALE
|
||||
else 0.0
|
||||
)
|
||||
for e in modimpl.evaluations
|
||||
],
|
||||
dtype=float,
|
||||
)
|
||||
@ -285,7 +285,7 @@ class ModuleImplResults:
|
||||
for (etudid, x) in self.evals_notes[evaluation_id].items()
|
||||
}
|
||||
|
||||
def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl):
|
||||
def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl) -> Evaluation | None:
|
||||
"""L'évaluation de rattrapage de ce module, ou None s'il n'en a pas.
|
||||
Rattrapage: la moyenne du module est la meilleure note entre moyenne
|
||||
des autres évals et la note eval rattrapage.
|
||||
@ -293,25 +293,41 @@ class ModuleImplResults:
|
||||
eval_list = [
|
||||
e
|
||||
for e in moduleimpl.evaluations
|
||||
if e.evaluation_type == scu.EVALUATION_RATTRAPAGE
|
||||
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE
|
||||
]
|
||||
if eval_list:
|
||||
return eval_list[0]
|
||||
return None
|
||||
|
||||
def get_evaluation_session2(self, moduleimpl: ModuleImpl):
|
||||
def get_evaluation_session2(self, moduleimpl: ModuleImpl) -> Evaluation | None:
|
||||
"""L'évaluation de deuxième session de ce module, ou None s'il n'en a pas.
|
||||
Session 2: remplace la note de moyenne des autres évals.
|
||||
"""
|
||||
eval_list = [
|
||||
e
|
||||
for e in moduleimpl.evaluations
|
||||
if e.evaluation_type == scu.EVALUATION_SESSION2
|
||||
if e.evaluation_type == Evaluation.EVALUATION_SESSION2
|
||||
]
|
||||
if eval_list:
|
||||
return eval_list[0]
|
||||
return None
|
||||
|
||||
def get_evaluations_bonus(self, modimpl: ModuleImpl) -> list[Evaluation]:
|
||||
"""Les évaluations bonus de ce module, ou liste vide s'il n'en a pas."""
|
||||
return [
|
||||
e
|
||||
for e in modimpl.evaluations
|
||||
if e.evaluation_type == Evaluation.EVALUATION_BONUS
|
||||
]
|
||||
|
||||
def get_evaluations_bonus_idx(self, modimpl: ModuleImpl) -> list[int]:
|
||||
"""Les indices des évaluations bonus"""
|
||||
return [
|
||||
i
|
||||
for (i, e) in enumerate(modimpl.evaluations)
|
||||
if e.evaluation_type == Evaluation.EVALUATION_BONUS
|
||||
]
|
||||
|
||||
|
||||
class ModuleImplResultsAPC(ModuleImplResults):
|
||||
"Calcul des moyennes de modules à la mode BUT"
|
||||
@ -356,7 +372,7 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
||||
# et dans dans evals_poids_etuds
|
||||
# (rappel: la comparaison est toujours false face à un NaN)
|
||||
# shape: (nb_etuds, nb_evals, nb_ues)
|
||||
poids_stacked = np.stack([evals_poids] * nb_etuds)
|
||||
poids_stacked = np.stack([evals_poids] * nb_etuds) # nb_etuds, nb_evals, nb_ues
|
||||
evals_poids_etuds = np.where(
|
||||
np.stack([self.evals_notes.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
||||
poids_stacked,
|
||||
@ -364,10 +380,20 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
||||
)
|
||||
# Calcule la moyenne pondérée sur les notes disponibles:
|
||||
evals_notes_stacked = np.stack([evals_notes_20] * nb_ues, axis=2)
|
||||
# evals_notes_stacked shape: nb_etuds, nb_evals, nb_ues
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
etuds_moy_module = np.sum(
|
||||
evals_poids_etuds * evals_notes_stacked, axis=1
|
||||
) / np.sum(evals_poids_etuds, axis=1)
|
||||
# etuds_moy_module shape: nb_etuds x nb_ues
|
||||
|
||||
# Application des évaluations bonus:
|
||||
etuds_moy_module = self.apply_bonus(
|
||||
etuds_moy_module,
|
||||
modimpl,
|
||||
evals_poids_df,
|
||||
evals_notes_stacked,
|
||||
)
|
||||
|
||||
# Session2 : quand elle existe, remplace la note de module
|
||||
eval_session2 = self.get_evaluation_session2(modimpl)
|
||||
@ -416,6 +442,30 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
||||
)
|
||||
return self.etuds_moy_module
|
||||
|
||||
def apply_bonus(
|
||||
self,
|
||||
etuds_moy_module: pd.DataFrame,
|
||||
modimpl: ModuleImpl,
|
||||
evals_poids_df: pd.DataFrame,
|
||||
evals_notes_stacked: np.ndarray,
|
||||
):
|
||||
"""Ajoute les points des évaluations bonus.
|
||||
Il peut y avoir un nb quelconque d'évaluations bonus.
|
||||
Les points sont directement ajoutés (ils peuvent être négatifs).
|
||||
"""
|
||||
evals_bonus = self.get_evaluations_bonus(modimpl)
|
||||
if not evals_bonus:
|
||||
return etuds_moy_module
|
||||
poids_stacked = np.stack([evals_poids_df.values] * len(etuds_moy_module))
|
||||
for evaluation in evals_bonus:
|
||||
eval_idx = evals_poids_df.index.get_loc(evaluation.id)
|
||||
etuds_moy_module += (
|
||||
evals_notes_stacked[:, eval_idx, :] * poids_stacked[:, eval_idx, :]
|
||||
)
|
||||
# Clip dans [0,20]
|
||||
etuds_moy_module.clip(0, 20, out=etuds_moy_module)
|
||||
return etuds_moy_module
|
||||
|
||||
|
||||
def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
||||
"""Charge poids des évaluations d'un module et retourne un dataframe
|
||||
@ -532,6 +582,13 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
||||
evals_coefs_etuds * evals_notes_20, axis=1
|
||||
) / np.sum(evals_coefs_etuds, axis=1)
|
||||
|
||||
# Application des évaluations bonus:
|
||||
etuds_moy_module = self.apply_bonus(
|
||||
etuds_moy_module,
|
||||
modimpl,
|
||||
evals_notes_20,
|
||||
)
|
||||
|
||||
# Session2 : quand elle existe, remplace la note de module
|
||||
eval_session2 = self.get_evaluation_session2(modimpl)
|
||||
if eval_session2:
|
||||
@ -571,3 +628,22 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
||||
)
|
||||
|
||||
return self.etuds_moy_module
|
||||
|
||||
def apply_bonus(
|
||||
self,
|
||||
etuds_moy_module: np.ndarray,
|
||||
modimpl: ModuleImpl,
|
||||
evals_notes_20: np.ndarray,
|
||||
):
|
||||
"""Ajoute les points des évaluations bonus.
|
||||
Il peut y avoir un nb quelconque d'évaluations bonus.
|
||||
Les points sont directement ajoutés (ils peuvent être négatifs).
|
||||
"""
|
||||
evals_bonus_idx = self.get_evaluations_bonus_idx(modimpl)
|
||||
if not evals_bonus_idx:
|
||||
return etuds_moy_module
|
||||
for eval_idx in evals_bonus_idx:
|
||||
etuds_moy_module += evals_notes_20[:, eval_idx]
|
||||
# Clip dans [0,20]
|
||||
etuds_moy_module.clip(0, 20, out=etuds_moy_module)
|
||||
return etuds_moy_module
|
||||
|
@ -23,8 +23,6 @@ MAX_EVALUATION_DURATION = datetime.timedelta(days=365)
|
||||
NOON = datetime.time(12, 00)
|
||||
DEFAULT_EVALUATION_TIME = datetime.time(8, 0)
|
||||
|
||||
VALID_EVALUATION_TYPES = {0, 1, 2}
|
||||
|
||||
|
||||
class Evaluation(db.Model):
|
||||
"""Evaluation (contrôle, examen, ...)"""
|
||||
@ -57,6 +55,17 @@ class Evaluation(db.Model):
|
||||
numero = db.Column(db.Integer, nullable=False, default=0)
|
||||
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
|
||||
|
||||
EVALUATION_NORMALE = 0 # valeurs stockées en base, ne pas changer !
|
||||
EVALUATION_RATTRAPAGE = 1
|
||||
EVALUATION_SESSION2 = 2
|
||||
EVALUATION_BONUS = 3
|
||||
VALID_EVALUATION_TYPES = {
|
||||
EVALUATION_NORMALE,
|
||||
EVALUATION_RATTRAPAGE,
|
||||
EVALUATION_SESSION2,
|
||||
EVALUATION_BONUS,
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f"""<Evaluation {self.id} {
|
||||
self.date_debut.isoformat() if self.date_debut else ''} "{
|
||||
@ -546,7 +555,7 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
||||
# --- evaluation_type
|
||||
try:
|
||||
data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0)
|
||||
if not data["evaluation_type"] in VALID_EVALUATION_TYPES:
|
||||
if not data["evaluation_type"] in Evaluation.VALID_EVALUATION_TYPES:
|
||||
raise ScoValueError("invalid evaluation_type value")
|
||||
except ValueError as exc:
|
||||
raise ScoValueError("invalid evaluation_type value") from exc
|
||||
|
@ -610,16 +610,19 @@ def _ue_mod_bulletin(
|
||||
e_dict["coef_txt"] = ""
|
||||
else:
|
||||
e_dict["coef_txt"] = scu.fmt_coef(e.coefficient)
|
||||
if e.evaluation_type == scu.EVALUATION_RATTRAPAGE:
|
||||
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
|
||||
e_dict["coef_txt"] = "rat."
|
||||
elif e.evaluation_type == scu.EVALUATION_SESSION2:
|
||||
elif e.evaluation_type == Evaluation.EVALUATION_SESSION2:
|
||||
e_dict["coef_txt"] = "Ses. 2"
|
||||
|
||||
if modimpl_results.evaluations_etat[e.id].nb_attente:
|
||||
mod_attente = True # une eval en attente dans ce module
|
||||
|
||||
if ((not is_malus) or (val != "NP")) and (
|
||||
(e.evaluation_type == scu.EVALUATION_NORMALE or not np.isnan(val))
|
||||
(
|
||||
e.evaluation_type == Evaluation.EVALUATION_NORMALE
|
||||
or not np.isnan(val)
|
||||
)
|
||||
):
|
||||
# ne liste pas les eval malus sans notes
|
||||
# ni les rattrapages et sessions 2 si pas de note
|
||||
|
@ -51,7 +51,7 @@ from reportlab.lib.colors import Color, blue
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table
|
||||
|
||||
from app.models import BulAppreciations
|
||||
from app.models import BulAppreciations, Evaluation
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import (
|
||||
gen_tables,
|
||||
@ -715,9 +715,15 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
eval_style = ""
|
||||
t = {
|
||||
"module": '<bullet indent="2mm">•</bullet> ' + e["name"],
|
||||
"coef": ("<i>" + e["coef_txt"] + "</i>")
|
||||
if prefs["bul_show_coef"]
|
||||
else "",
|
||||
"coef": (
|
||||
(
|
||||
f"<i>{e['coef_txt']}</i>"
|
||||
if e["evaluation_type"] != Evaluation.EVALUATION_BONUS
|
||||
else "bonus"
|
||||
)
|
||||
if prefs["bul_show_coef"]
|
||||
else ""
|
||||
),
|
||||
"_hidden": hidden,
|
||||
"_module_target": e["target_html"],
|
||||
# '_module_help' : ,
|
||||
|
@ -183,7 +183,8 @@ def evaluation_create_form(
|
||||
{
|
||||
"size": 6,
|
||||
"type": "float", # peut être négatif (!)
|
||||
"explanation": "coef. dans le module (choisi librement par l'enseignant, non utilisé pour rattrapage et 2ème session)",
|
||||
"explanation": """coef. dans le module (choisi librement par
|
||||
l'enseignant, non utilisé pour rattrapage, 2ème session et bonus)""",
|
||||
"allow_null": False,
|
||||
},
|
||||
)
|
||||
@ -195,7 +196,7 @@ def evaluation_create_form(
|
||||
"size": 4,
|
||||
"type": "float",
|
||||
"title": "Notes de 0 à",
|
||||
"explanation": f"barème (note max actuelle: {min_note_max_str})",
|
||||
"explanation": f"""barème (note max actuelle: {min_note_max_str}).""",
|
||||
"allow_null": False,
|
||||
"max_value": scu.NOTES_MAX,
|
||||
"min_value": min_note_max,
|
||||
@ -206,7 +207,8 @@ def evaluation_create_form(
|
||||
{
|
||||
"size": 36,
|
||||
"type": "text",
|
||||
"explanation": """type d'évaluation, apparait sur le bulletins longs. Exemples: "contrôle court", "examen de TP", "examen final".""",
|
||||
"explanation": """type d'évaluation, apparait sur le bulletins longs.
|
||||
Exemples: "contrôle court", "examen de TP", "examen final".""",
|
||||
},
|
||||
),
|
||||
(
|
||||
@ -230,16 +232,20 @@ def evaluation_create_form(
|
||||
{
|
||||
"input_type": "menu",
|
||||
"title": "Modalité",
|
||||
"allowed_values": (
|
||||
scu.EVALUATION_NORMALE,
|
||||
scu.EVALUATION_RATTRAPAGE,
|
||||
scu.EVALUATION_SESSION2,
|
||||
),
|
||||
"allowed_values": Evaluation.VALID_EVALUATION_TYPES,
|
||||
"type": "int",
|
||||
"labels": (
|
||||
"Normale",
|
||||
"Rattrapage (remplace si meilleure note)",
|
||||
"Deuxième session (remplace toujours)",
|
||||
(
|
||||
"Bonus "
|
||||
+ (
|
||||
"(pondéré par poids et ajouté aux moyennes de ce module)"
|
||||
if is_apc
|
||||
else "(ajouté à la moyenne de ce module)"
|
||||
)
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
@ -251,7 +257,8 @@ def evaluation_create_form(
|
||||
{
|
||||
"size": 6,
|
||||
"type": "float",
|
||||
"explanation": "importance de l'évaluation (multiplie les poids ci-dessous)",
|
||||
"explanation": """importance de l'évaluation (multiplie les poids ci-dessous).
|
||||
Non utilisé pour les bonus.""",
|
||||
"allow_null": False,
|
||||
},
|
||||
),
|
||||
|
@ -217,19 +217,9 @@ def do_evaluation_etat(
|
||||
|
||||
gr_incomplets = list(group_nb_missing.keys())
|
||||
gr_incomplets.sort()
|
||||
if (
|
||||
(total_nb_missing > 0)
|
||||
and (E["evaluation_type"] != scu.EVALUATION_RATTRAPAGE)
|
||||
and (E["evaluation_type"] != scu.EVALUATION_SESSION2)
|
||||
):
|
||||
complete = False
|
||||
else:
|
||||
complete = True
|
||||
|
||||
complete = (
|
||||
(total_nb_missing == 0)
|
||||
or (E["evaluation_type"] == scu.EVALUATION_RATTRAPAGE)
|
||||
or (E["evaluation_type"] == scu.EVALUATION_SESSION2)
|
||||
complete = (total_nb_missing == 0) or (
|
||||
E["evaluation_type"] != Evaluation.EVALUATION_NORMALE
|
||||
)
|
||||
evalattente = (total_nb_missing > 0) and (
|
||||
(total_nb_missing == total_nb_att) or E["publish_incomplete"]
|
||||
@ -498,13 +488,14 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, fmt="html"):
|
||||
"""Experimental: un tableau indiquant pour chaque évaluation
|
||||
le nombre de jours avant la publication des notes.
|
||||
|
||||
N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus.
|
||||
N'indique que les évaluations "normales" (pas rattrapage, ni bonus, ni session2,
|
||||
ni celles des modules de bonus/malus).
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
evaluations = formsemestre.get_evaluations()
|
||||
rows = []
|
||||
for e in evaluations:
|
||||
if (e.evaluation_type != scu.EVALUATION_NORMALE) or (
|
||||
if (e.evaluation_type != Evaluation.EVALUATION_NORMALE) or (
|
||||
e.moduleimpl.module.module_type == ModuleType.MALUS
|
||||
):
|
||||
continue
|
||||
@ -610,13 +601,17 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True)
|
||||
# Indique l'UE
|
||||
ue = modimpl.module.ue
|
||||
H.append(f"<p><b>UE : {ue.acronyme}</b></p>")
|
||||
if (
|
||||
modimpl.module.module_type == ModuleType.MALUS
|
||||
or evaluation.evaluation_type == Evaluation.EVALUATION_BONUS
|
||||
):
|
||||
# store min/max values used by JS client-side checks:
|
||||
H.append(
|
||||
"""<span id="eval_note_min" class="sco-hidden">-20.</span>
|
||||
<span id="eval_note_max" class="sco-hidden">20.</span>"""
|
||||
)
|
||||
else:
|
||||
# date et absences (pas pour evals de malus)
|
||||
# date et absences (pas pour evals bonus ni des modules de malus)
|
||||
if evaluation.date_debut is not None:
|
||||
H.append(f"<p>Réalisée le <b>{evaluation.descr_date()}</b> ")
|
||||
group_id = sco_groups.get_default_group(modimpl.formsemestre_id)
|
||||
|
@ -490,9 +490,9 @@ def _make_table_notes(
|
||||
rlinks = {"_table_part": "head"}
|
||||
for e in evaluations:
|
||||
rlinks[e.id] = "afficher"
|
||||
rlinks[
|
||||
"_" + str(e.id) + "_help"
|
||||
] = "afficher seulement les notes de cette évaluation"
|
||||
rlinks["_" + str(e.id) + "_help"] = (
|
||||
"afficher seulement les notes de cette évaluation"
|
||||
)
|
||||
rlinks["_" + str(e.id) + "_target"] = url_for(
|
||||
"notes.evaluation_listenotes",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
@ -709,9 +709,9 @@ def _add_eval_columns(
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
||||
|
||||
if evaluation.date_debut:
|
||||
titles[
|
||||
evaluation.id
|
||||
] = f"{evaluation.description} ({evaluation.date_debut.strftime('%d/%m/%Y')})"
|
||||
titles[evaluation.id] = (
|
||||
f"{evaluation.description} ({evaluation.date_debut.strftime('%d/%m/%Y')})"
|
||||
)
|
||||
else:
|
||||
titles[evaluation.id] = f"{evaluation.description} "
|
||||
|
||||
@ -820,14 +820,17 @@ def _add_eval_columns(
|
||||
row_moys[evaluation.id] = scu.fmt_note(
|
||||
sum_notes / nb_notes, keep_numeric=keep_numeric
|
||||
)
|
||||
row_moys[
|
||||
"_" + str(evaluation.id) + "_help"
|
||||
] = "moyenne sur %d notes (%s le %s)" % (
|
||||
nb_notes,
|
||||
evaluation.description,
|
||||
evaluation.date_debut.strftime("%d/%m/%Y")
|
||||
if evaluation.date_debut
|
||||
else "",
|
||||
row_moys["_" + str(evaluation.id) + "_help"] = (
|
||||
"moyenne sur %d notes (%s le %s)"
|
||||
% (
|
||||
nb_notes,
|
||||
evaluation.description,
|
||||
(
|
||||
evaluation.date_debut.strftime("%d/%m/%Y")
|
||||
if evaluation.date_debut
|
||||
else ""
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
row_moys[evaluation.id] = ""
|
||||
@ -884,8 +887,9 @@ def _add_moymod_column(
|
||||
row["_" + col_id + "_td_attrs"] = ' class="moyenne" '
|
||||
if etudid in inscrits and not isinstance(val, str):
|
||||
notes.append(val)
|
||||
nb_notes = nb_notes + 1
|
||||
sum_notes += val
|
||||
if not np.isnan(val):
|
||||
nb_notes = nb_notes + 1
|
||||
sum_notes += val
|
||||
row_coefs[col_id] = "(avec abs)"
|
||||
if is_apc:
|
||||
row_poids[col_id] = "à titre indicatif"
|
||||
|
@ -519,13 +519,15 @@ def _ligne_evaluation(
|
||||
partition_id=partition_id,
|
||||
select_first_partition=True,
|
||||
)
|
||||
if evaluation.evaluation_type in (
|
||||
scu.EVALUATION_RATTRAPAGE,
|
||||
scu.EVALUATION_SESSION2,
|
||||
):
|
||||
if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
|
||||
tr_class = "mievr mievr_rattr"
|
||||
elif evaluation.evaluation_type == Evaluation.EVALUATION_SESSION2:
|
||||
tr_class = "mievr mievr_session2"
|
||||
elif evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
|
||||
tr_class = "mievr mievr_bonus"
|
||||
else:
|
||||
tr_class = "mievr"
|
||||
|
||||
if not evaluation.visibulletin:
|
||||
tr_class += " non_visible_inter"
|
||||
tr_class_1 = "mievr"
|
||||
@ -563,13 +565,17 @@ def _ligne_evaluation(
|
||||
}" class="mievr_evalnodate">Évaluation sans date</a>"""
|
||||
)
|
||||
H.append(f" <em>{evaluation.description or ''}</em>")
|
||||
if evaluation.evaluation_type == scu.EVALUATION_RATTRAPAGE:
|
||||
if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
|
||||
H.append(
|
||||
"""<span class="mievr_rattr" title="remplace si meilleure note">rattrapage</span>"""
|
||||
)
|
||||
elif evaluation.evaluation_type == scu.EVALUATION_SESSION2:
|
||||
elif evaluation.evaluation_type == Evaluation.EVALUATION_SESSION2:
|
||||
H.append(
|
||||
"""<span class="mievr_rattr" title="remplace autres notes">session 2</span>"""
|
||||
"""<span class="mievr_session2" title="remplace autres notes">session 2</span>"""
|
||||
)
|
||||
elif evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
|
||||
H.append(
|
||||
"""<span class="mievr_bonus" title="s'ajoute aux moyennes de ce module">bonus</span>"""
|
||||
)
|
||||
#
|
||||
if etat["last_modif"]:
|
||||
|
@ -134,12 +134,12 @@ def _displayNote(val):
|
||||
return val
|
||||
|
||||
|
||||
def _check_notes(notes: list[(int, float)], evaluation: Evaluation):
|
||||
# XXX typehint : float or str
|
||||
def _check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
|
||||
"""notes is a list of tuples (etudid, value)
|
||||
mod is the module (used to ckeck type, for malus)
|
||||
returns list of valid notes (etudid, float value)
|
||||
and 4 lists of etudid: etudids_invalids, etudids_without_notes, etudids_absents, etudid_to_suppress
|
||||
and 4 lists of etudid:
|
||||
etudids_invalids, etudids_without_notes, etudids_absents, etudid_to_suppress
|
||||
"""
|
||||
note_max = evaluation.note_max or 0.0
|
||||
module: Module = evaluation.moduleimpl.module
|
||||
@ -148,7 +148,10 @@ def _check_notes(notes: list[(int, float)], evaluation: Evaluation):
|
||||
scu.ModuleType.RESSOURCE,
|
||||
scu.ModuleType.SAE,
|
||||
):
|
||||
note_min = scu.NOTES_MIN
|
||||
if evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
|
||||
note_min, note_max = -20, 20
|
||||
else:
|
||||
note_min = scu.NOTES_MIN
|
||||
elif module.module_type == ModuleType.MALUS:
|
||||
note_min = -20.0
|
||||
else:
|
||||
|
@ -175,7 +175,7 @@ def external_ue_inscrit_et_note(
|
||||
note_max=20.0,
|
||||
coefficient=1.0,
|
||||
publish_incomplete=True,
|
||||
evaluation_type=scu.EVALUATION_NORMALE,
|
||||
evaluation_type=Evaluation.EVALUATION_NORMALE,
|
||||
visibulletin=False,
|
||||
description="note externe",
|
||||
)
|
||||
|
@ -454,10 +454,6 @@ NOTES_MENTIONS_LABS = (
|
||||
"Excellent",
|
||||
)
|
||||
|
||||
EVALUATION_NORMALE = 0
|
||||
EVALUATION_RATTRAPAGE = 1
|
||||
EVALUATION_SESSION2 = 2
|
||||
|
||||
# Dates et années scolaires
|
||||
# Ces dates "pivot" sont paramétrables dans les préférences générales
|
||||
# on donne ici les valeurs par défaut.
|
||||
|
@ -273,6 +273,10 @@ section>div:nth-child(1) {
|
||||
min-width: 80px;
|
||||
display: inline-block;
|
||||
}
|
||||
div.eval-bonus {
|
||||
color: #197614;
|
||||
background-color: pink;
|
||||
}
|
||||
|
||||
.ueBonus,
|
||||
.ueBonus h3 {
|
||||
|
@ -2103,11 +2103,11 @@ tr.mievr {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
tr.mievr_rattr {
|
||||
tr.mievr_rattr, tr.mievr_session2, tr.mievr_bonus {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
|
||||
span.mievr_rattr {
|
||||
span.mievr_rattr, span.mievr_session2, span.mievr_bonus {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
font-size: 80%;
|
||||
@ -4743,6 +4743,10 @@ table.table_recap th.col_malus {
|
||||
font-weight: bold;
|
||||
color: rgb(165, 0, 0);
|
||||
}
|
||||
table.table_recap td.col_eval_bonus,
|
||||
table.table_recap th.col_eval_bonus {
|
||||
color: #90c;
|
||||
}
|
||||
|
||||
table.table_recap tr.ects td {
|
||||
color: rgb(160, 86, 3);
|
||||
|
@ -491,14 +491,15 @@ class releveBUT extends HTMLElement {
|
||||
let output = "";
|
||||
evaluations.forEach((evaluation) => {
|
||||
output += `
|
||||
<div class=eval>
|
||||
<div class="eval ${evaluation.evaluation_type == 3 ? "eval-bonus" : ""}">
|
||||
<div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div>
|
||||
<div>
|
||||
${evaluation.note.value}
|
||||
<em>Coef. ${evaluation.coef ?? "*"}</em>
|
||||
<em>${evaluation.evaluation_type == 0 ? "Coef." : evaluation.evaluation_type == 3 ? "Bonus" : ""
|
||||
} ${evaluation.coef ?? ""}</em>
|
||||
</div>
|
||||
<div class=complement>
|
||||
<div>Coef</div><div>${evaluation.coef}</div>
|
||||
<div>${evaluation.evaluation_type == 0 ? "Coef." : ""}</div><div>${evaluation.coef ?? ""}</div>
|
||||
<div>Max. promo.</div><div>${evaluation.note.max}</div>
|
||||
<div>Moy. promo.</div><div>${evaluation.note.moy}</div>
|
||||
<div>Min. promo.</div><div>${evaluation.note.min}</div>
|
||||
|
@ -13,7 +13,7 @@ import numpy as np
|
||||
from app import db
|
||||
from app.auth.models import User
|
||||
from app.comp.res_common import ResultatsSemestre
|
||||
from app.models import Identite, FormSemestre, UniteEns
|
||||
from app.models import Identite, Evaluation, FormSemestre, UniteEns
|
||||
from app.scodoc.codes_cursus import UE_SPORT, DEF
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_groups
|
||||
@ -405,15 +405,22 @@ class TableRecap(tb.Table):
|
||||
val = notes_db[etudid]["value"]
|
||||
else:
|
||||
# Note manquante mais prise en compte immédiate: affiche ATT
|
||||
val = scu.NOTES_ATTENTE
|
||||
val = (
|
||||
scu.NOTES_ATTENTE
|
||||
if e.evaluation_type != Evaluation.EVALUATION_BONUS
|
||||
else ""
|
||||
)
|
||||
content = self.fmt_note(val)
|
||||
classes = col_classes + [
|
||||
{
|
||||
"ABS": "abs",
|
||||
"ATT": "att",
|
||||
"EXC": "exc",
|
||||
}.get(content, "")
|
||||
]
|
||||
if e.evaluation_type != Evaluation.EVALUATION_BONUS:
|
||||
classes = col_classes + [
|
||||
{
|
||||
"ABS": "abs",
|
||||
"ATT": "att",
|
||||
"EXC": "exc",
|
||||
}.get(content, "")
|
||||
]
|
||||
else:
|
||||
classes = col_classes + ["col_eval_bonus"]
|
||||
row.add_cell(
|
||||
col_id, title, content, group="eval", classes=classes
|
||||
)
|
||||
|
@ -8,13 +8,15 @@
|
||||
</p>
|
||||
{%if is_apc%}
|
||||
<p class="help help_but">
|
||||
Dans le BUT, une évaluation peut évaluer différents apprentissages critiques... (à compléter)
|
||||
Le coefficient est multiplié par les poids vers chaque UE.
|
||||
Dans le BUT, une évaluation peut évaluer différents apprentissages critiques,
|
||||
et les poids permettent de moduler l'importance de l'évaluation pour
|
||||
chaque compétence (UE).
|
||||
Le coefficient de l'évaluation est multiplié par les poids vers chaque UE.
|
||||
</p>
|
||||
{%endif%}
|
||||
<p class="help">
|
||||
Ne pas confondre ce coefficient avec le coefficient du module, qui est
|
||||
lui fixé par le programme pédagogique (le PPN pour les DUT) et pondère
|
||||
lui fixé par le programme pédagogique (le PN pour les BUT) et pondère
|
||||
les moyennes de chaque module pour obtenir les moyennes d'UE et la
|
||||
moyenne générale.
|
||||
</p>
|
||||
@ -22,17 +24,31 @@
|
||||
L'option <em>Visible sur bulletins</em> indique que la note sera
|
||||
reportée sur les bulletins en version dite "intermédiaire" (dans cette
|
||||
version, on peut ne faire apparaitre que certaines notes, en sus des
|
||||
moyennes de modules. Attention, cette option n'empêche pas la
|
||||
moyennes de modules). Attention, cette option n'empêche pas la
|
||||
publication sur les bulletins en version "longue" (la note est donc
|
||||
visible par les étudiants sur le portail).
|
||||
</p>
|
||||
<p class="help">
|
||||
Les évaluations bonus sont particulières:
|
||||
</p>
|
||||
<ul>
|
||||
<li>la valeur est ajoutée à la moyenne du module;</li>
|
||||
<li>le bonus peut être négatif (malus);
|
||||
</li>
|
||||
<li>le bonus ne s'applique pas aux notes de rattrapage et deuxième session;
|
||||
</li>
|
||||
<li>le coefficient est ignoré, mais en BUT le bonus vers une UE est multiplié
|
||||
par le poids correspondant (par défaut égal à 1);
|
||||
</li>
|
||||
<li>les notes de bonus sont prises en compte même si incomplètes.</li>
|
||||
</ul>
|
||||
<p class="help">
|
||||
Les modalités "rattrapage" et "deuxième session" définissent des
|
||||
évaluations prises en compte de façon spéciale:
|
||||
</p>
|
||||
<ul>
|
||||
<li>les notes d'une évaluation de "rattrapage" remplaceront les moyennes
|
||||
du module <em>si elles sont meilleures que celles calculées</em>.
|
||||
du module <em>si elles sont meilleures que celles calculées;</em>.
|
||||
</li>
|
||||
<li>les notes de "deuxième session" remplacent, lorsqu'elles sont
|
||||
saisies, la moyenne de l'étudiant à ce module, même si la note de
|
||||
|
@ -1,19 +1,20 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.944"
|
||||
SCOVERSION = "9.6.945"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
SCONEWS = """
|
||||
<h4>Année 2023</h4>
|
||||
<h4>Année 2023-2024</h4>
|
||||
<ul>
|
||||
|
||||
<li>ScoDoc 9.6 (juillet 2023)</li>
|
||||
<li>ScoDoc 9.6 (2023-2024)</li>
|
||||
<ul>
|
||||
<li>Nouveaux bulletins BUT compacts</li>
|
||||
<li>Nouvelle gestion des absences et assiduité</li>
|
||||
<li>Mise à jour logiciels: Debian 12, Python 3.11, ...</li>
|
||||
<li>Evaluations bonus</li>
|
||||
</ul>
|
||||
|
||||
<li>ScoDoc 9.5 (juillet 2023)</li>
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Test calculs rattrapages
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import app
|
||||
@ -68,7 +69,7 @@ def test_notes_rattrapage(test_client):
|
||||
date_debut=datetime.datetime(2020, 1, 2),
|
||||
description="evaluation rattrapage",
|
||||
coefficient=1.0,
|
||||
evaluation_type=scu.EVALUATION_RATTRAPAGE,
|
||||
evaluation_type=Evaluation.EVALUATION_RATTRAPAGE,
|
||||
)
|
||||
etud = etuds[0]
|
||||
_, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=12.0)
|
||||
@ -144,7 +145,7 @@ def test_notes_rattrapage(test_client):
|
||||
date_debut=datetime.datetime(2020, 1, 2),
|
||||
description="evaluation session 2",
|
||||
coefficient=1.0,
|
||||
evaluation_type=scu.EVALUATION_SESSION2,
|
||||
evaluation_type=Evaluation.EVALUATION_SESSION2,
|
||||
)
|
||||
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
Loading…
Reference in New Issue
Block a user