Calcul moyenne UE BUT progressif (admet modules sans notes)

This commit is contained in:
Emmanuel Viennet 2021-12-08 21:49:13 +01:00
parent 270d03057f
commit 11b3f64319
7 changed files with 117 additions and 108 deletions

View File

@ -120,8 +120,13 @@ class ResultatsSemestreBUT:
"total": ue.ects, "total": ue.ects,
}, },
"competence": None, # XXX TODO lien avec référentiel "competence": None, # XXX TODO lien avec référentiel
"moyenne": fmt_note(self.etud_moy_ue[ue.id].mean()), "moyenne": {
"bonus": None, # XXX TODO "value": fmt_note(self.etud_moy_ue[ue.id][etud.id]),
"min": fmt_note(self.etud_moy_ue[ue.id].min()),
"max": fmt_note(self.etud_moy_ue[ue.id].max()),
"moy": fmt_note(self.etud_moy_ue[ue.id].mean()),
},
"bonus": 17.8, # None, # XXX TODO
"malus": None, # XXX TODO voir ce qui est ici "malus": None, # XXX TODO voir ce qui est ici
"capitalise": None, # "AAAA-MM-JJ" TODO "capitalise": None, # "AAAA-MM-JJ" TODO
"ressources": self.etud_ue_mod_results(etud, ue, self.ressources), "ressources": self.etud_ue_mod_results(etud, ue, self.ressources),

View File

@ -186,25 +186,25 @@ def compute_ue_moys(
modimpl_coefs = modimpl_coefs_df.values modimpl_coefs = modimpl_coefs_df.values
# Duplique les inscriptions sur les UEs: # Duplique les inscriptions sur les UEs:
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues, axis=2) modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues, axis=2)
# Enlève les NaN du numérateur: # Enlève les NaN du numérateur:
# si on veut prendre en compte les module avec notes neutralisées ? # si on veut prendre en compte les modules avec notes neutralisées ?
# sem_cube_no_nan = np.nan_to_num(sem_cube, nan=0.0) sem_cube_no_nan = np.nan_to_num(sem_cube, nan=0.0)
# Ne prend pas en compte les notes des étudiants non inscrits au module: # Ne prend pas en compte les notes des étudiants non inscrits au module:
# Annule les notes: # Annule les notes:
sem_cube_inscrits = np.where(modimpl_inscr_stacked, sem_cube, 0.0) sem_cube_inscrits = np.where(modimpl_inscr_stacked, sem_cube_no_nan, 0.0)
# Annule les coefs des modules où l'étudiant n'est pas inscrit: # Annule les coefs des modules où l'étudiant n'est pas inscrit:
modimpl_coefs_etuds = np.where( modimpl_coefs_etuds = np.where(
modimpl_inscr_stacked, np.stack([modimpl_coefs.T] * nb_etuds), 0.0 modimpl_inscr_stacked, np.stack([modimpl_coefs.T] * nb_etuds), 0.0
) )
# Annule les coefs des modules NaN
modimpl_coefs_etuds_no_nan = np.where(np.isnan(sem_cube), 0.0, modimpl_coefs_etuds)
# #
# Version vectorisée # Version vectorisée
# #
etud_moy_ue = np.sum(modimpl_coefs_etuds * sem_cube_inscrits, axis=1) / np.sum( etud_moy_ue = np.sum(
modimpl_coefs_etuds, axis=1 modimpl_coefs_etuds_no_nan * sem_cube_inscrits, axis=1
) ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
return pd.DataFrame( return pd.DataFrame(
etud_moy_ue, index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index etud_moy_ue, index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
) )

View File

@ -528,21 +528,18 @@ def module_edit(module_id=None):
("formation_id", {"input_type": "hidden"}), ("formation_id", {"input_type": "hidden"}),
("ue_id", {"input_type": "hidden"}), ("ue_id", {"input_type": "hidden"}),
("module_id", {"input_type": "hidden"}), ("module_id", {"input_type": "hidden"}),
(
"ue_matiere_id",
{
"input_type": "menu" if not is_apc else "hidden",
"title": "Matière",
"explanation": "un module appartient à une seule matière.",
"labels": mat_names,
"allowed_values": ue_mat_ids,
"enabled": unlocked and not is_apc, # pas d'édition des matieres en BUT
},
),
] ]
if not is_apc:
descr += [
(
"ue_matiere_id",
{
"input_type": "menu",
"title": "Matière",
"explanation": "un module appartient à une seule matière.",
"labels": mat_names,
"allowed_values": ue_mat_ids,
"enabled": unlocked,
},
),
]
if is_apc: if is_apc:
# le semestre du module est toujours celui de son UE # le semestre du module est toujours celui de son UE
descr += [ descr += [

View File

@ -816,7 +816,7 @@ def _add_apc_columns(
etuds_moy_module = moy_mod.compute_module_moy( etuds_moy_module = moy_mod.compute_module_moy(
evals_notes, evals_poids, evaluations, evaluations_completes evals_notes, evals_poids, evaluations, evaluations_completes
) )
ue_coefs = models.ModuleImpl.query.get(moduleimpl_id).module.ue_coefs
for row in rows: for row in rows:
for ue in ues: for ue in ues:
moy_ue = etuds_moy_module[ue.id].get(row["etudid"], "?") moy_ue = etuds_moy_module[ue.id].get(row["etudid"], "?")
@ -826,4 +826,7 @@ def _add_apc_columns(
col_id = f"moy_ue_{ue.id}" col_id = f"moy_ue_{ue.id}"
titles[col_id] = ue.acronyme titles[col_id] = ue.acronyme
columns_ids.append(col_id) columns_ids.append(col_id)
row_coefs[f"moy_ue_{ue.id}"] = "m" row_coefs[f"moy_ue_{ue.id}"] = [uc for uc in ue_coefs if uc.ue_id == ue.id][
0
].coef
row_coefs[f"_moy_ue_{ue.id}_td_attrs"] = ' class="coef_mod_ue" '

View File

@ -1066,6 +1066,12 @@ table.notes_evaluation td.moy_ue {
color:rgb(1, 116, 96); color:rgb(1, 116, 96);
} }
td.coef_mod_ue {
font-style: normal;
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%;

View File

@ -1,59 +1,59 @@
/* Il manque : /* Il manque :
- rangs - rangs
- Synthèse : moyenne UE - Synthèse : moyenne UE
- Synthèse : min, max, moy classe - Synthèse : min, max, moy classe
- Synthèse : absences - Synthèse : absences
- Eval : absences - Eval : absences
Moi : Moi :
"show_codemodules" :true, "show_codemodules" :true,
"show_minmax": true, "show_minmax": true,
"show_minmax_eval": true, "show_minmax_eval": true,
"show_minmax_mod": false, "show_minmax_mod": false,
"show_mod_rangs": false, "show_mod_rangs": false,
"show_moypromo": true, "show_moypromo": true,
"show_rangs": true, "show_rangs": true,
"show_ue_cap_current": true, "show_ue_cap_current": true,
"show_ue_cap_details": true, "show_ue_cap_details": true,
"show_ue_rangs": true, "show_ue_rangs": true,
"show_uevalid": true, "show_uevalid": true,
*/ */
/*****************************/ /*****************************/
/* Gestionnaire d'événements */ /* Gestionnaire d'événements */
/*****************************/ /*****************************/
document.querySelectorAll(".CTA_Liste").forEach(e => { document.querySelectorAll(".CTA_Liste").forEach(e => {
e.addEventListener("click", listeOnOff) e.addEventListener("click", listeOnOff)
}) })
function listeOnOff() { function listeOnOff() {
this.parentElement.parentElement.classList.toggle("listeOff") this.parentElement.parentElement.classList.toggle("listeOff")
} }
/*****************************/ /*****************************/
/* Recupération et affichage */ /* Recupération et affichage */
/*****************************/ /*****************************/
fetch(dataSrc) fetch(dataSrc)
.then(r => { return r.json() }) .then(r => { return r.json() })
.then(json => showData(json)) .then(json => showData(json))
function showData(data) { function showData(data) {
showInformations(data); showInformations(data);
showSemestre(data); showSemestre(data);
showSynthese(data); showSynthese(data);
showEvaluations(data); showEvaluations(data);
setOptions(data.options); setOptions(data.options);
document.body.classList.add("ready"); document.body.classList.add("ready");
} }
/********************************/ /********************************/
/* Informations sur l'étudiant */ /* Informations sur l'étudiant */
/********************************/ /********************************/
function showInformations(data) { function showInformations(data) {
document.querySelector(".studentPic").src = data.etudiant.photo_url || "default_Student.svg"; document.querySelector(".studentPic").src = data.etudiant.photo_url || "default_Student.svg";
let output = ` let output = `
<div class=info_etudiant> <div class=info_etudiant>
<div class=civilite> <div class=civilite>
${civilite(data.etudiant.civilite)} ${civilite(data.etudiant.civilite)}
@ -70,16 +70,16 @@ function showInformations(data) {
</div> </div>
`; `;
document.querySelector(".infoEtudiant").innerHTML = output; document.querySelector(".infoEtudiant").innerHTML = output;
} }
/*******************************/ /*******************************/
/* Information sur le semestre */ /* Information sur le semestre */
/*******************************/ /*******************************/
function showSemestre(data) { function showSemestre(data) {
document.querySelector("h2").innerHTML += data.semestre.numero; document.querySelector("h2").innerHTML += data.semestre.numero;
document.querySelector(".dateInscription").innerHTML += ISOToDate(data.semestre.inscription); document.querySelector(".dateInscription").innerHTML += ISOToDate(data.semestre.inscription);
let output = ` let output = `
<div> <div>
<div class=enteteSemestre>Moyenne</div><div class=enteteSemestre>${data.semestre.notes.value}</div> <div class=enteteSemestre>Moyenne</div><div class=enteteSemestre>${data.semestre.notes.value}</div>
<div class=rang>Rang :</div><div class=rang>${data.semestre.rang.value} / ${data.semestre.rang.total}</div> <div class=rang>Rang :</div><div class=rang>${data.semestre.rang.value} / ${data.semestre.rang.total}</div>
@ -88,7 +88,7 @@ function showSemestre(data) {
<div>Min. promo. :</div><div>${data.semestre.notes.min}</div> <div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
</div> </div>
${data.semestre.groupes.map(groupe => { ${data.semestre.groupes.map(groupe => {
return ` return `
<div> <div>
<div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div> <div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div>
<div class=rang>Rang :</div><div class=rang>${groupe.rang.value} / ${groupe.rang.total}</div> <div class=rang>Rang :</div><div class=rang>${groupe.rang.value} / ${groupe.rang.total}</div>
@ -97,25 +97,25 @@ function showSemestre(data) {
<div>Min. groupe :</div><div>${groupe.notes.min}</div> <div>Min. groupe :</div><div>${groupe.notes.min}</div>
</div> </div>
`; `;
}).join("") }).join("")
} }
`; `;
document.querySelector(".infoSemestre").innerHTML = output; document.querySelector(".infoSemestre").innerHTML = output;
} }
/*******************************/ /*******************************/
/* Synthèse */ /* Synthèse */
/*******************************/ /*******************************/
function showSynthese(data) { function showSynthese(data) {
let output = ``; let output = ``;
Object.entries(data.ues).forEach(([ue, dataUE]) => { Object.entries(data.ues).forEach(([ue, dataUE]) => {
output += ` output += `
<div class=ue> <div class=ue>
<h3> <h3>
${(dataUE.competence) ? dataUE.competence + " - " : ""}${ue} ${(dataUE.competence) ? dataUE.competence + " - " : ""}${ue}
</h3> </h3>
<div> <div>
<div class=moyenne>Moyenne&nbsp;:&nbsp;${dataUE.moyenne?.value || "-"}</div> <div class=moyenne>Moyenn&nbsp;:&nbsp;${dataUE.moyenne?.value || "-"}</div>
<div class=info> <div class=info>
Bonus&nbsp;:&nbsp;${dataUE.bonus || 0}&nbsp;- Bonus&nbsp;:&nbsp;${dataUE.bonus || 0}&nbsp;-
Malus&nbsp;:&nbsp;${dataUE.malus || 0} Malus&nbsp;:&nbsp;${dataUE.malus || 0}
@ -132,15 +132,15 @@ function showSynthese(data) {
${synthese(dataUE.ressources)} ${synthese(dataUE.ressources)}
${synthese(dataUE.saes)} ${synthese(dataUE.saes)}
`; `;
}); });
document.querySelector(".synthese").innerHTML = output; document.querySelector(".synthese").innerHTML = output;
function synthese(modules) { function synthese(modules) {
let output = ""; let output = "";
Object.entries(modules).forEach(([module, dataModule]) => { Object.entries(modules).forEach(([module, dataModule]) => {
let titre = data.ressources[module]?.titre || data.saes[module]?.titre; let titre = data.ressources[module]?.titre || data.saes[module]?.titre;
let url = data.ressources[module]?.url || data.saes[module]?.url; let url = data.ressources[module]?.url || data.saes[module]?.url;
output += ` output += `
<div class=syntheseModule> <div class=syntheseModule>
<div><a href="${url}">${module}&nbsp;- ${titre}</a></div> <div><a href="${url}">${module}&nbsp;- ${titre}</a></div>
<div> <div>
@ -149,21 +149,21 @@ function showSynthese(data) {
</div> </div>
</div> </div>
`; `;
}) })
return output; return output;
} }
} }
/*******************************/ /*******************************/
/* Evaluations */ /* Evaluations */
/*******************************/ /*******************************/
function showEvaluations(data) { function showEvaluations(data) {
document.querySelector(".evaluations").innerHTML = module(data.ressources); document.querySelector(".evaluations").innerHTML = module(data.ressources);
document.querySelector(".sae").innerHTML += module(data.saes); document.querySelector(".sae").innerHTML += module(data.saes);
function module(module) { function module(module) {
let output = ""; let output = "";
Object.entries(module).forEach(([numero, content]) => { Object.entries(module).forEach(([numero, content]) => {
output += ` output += `
<div> <div>
<div class=module> <div class=module>
<h3><a href="${content.url}">${numero} - ${content.titre}</a></h3> <h3><a href="${content.url}">${numero} - ${content.titre}</a></h3>
@ -183,14 +183,14 @@ function showEvaluations(data) {
${evaluation(content.evaluations)} ${evaluation(content.evaluations)}
</div> </div>
`; `;
}) })
return output; return output;
} }
function evaluation(evaluations) { function evaluation(evaluations) {
let output = ""; let output = "";
evaluations.forEach(eval => { evaluations.forEach(eval => {
output += ` output += `
<div class=eval> <div class=eval>
<div>${eval.description}</div> <div>${eval.description}</div>
<div> <div>
@ -203,28 +203,28 @@ function showEvaluations(data) {
<div>Moy. promo.</div><div>${eval.note.moy}</div> <div>Moy. promo.</div><div>${eval.note.moy}</div>
<div>Min. promo.</div><div>${eval.note.min}</div> <div>Min. promo.</div><div>${eval.note.min}</div>
${Object.entries(eval.poids).map(([UE, poids]) => { ${Object.entries(eval.poids).map(([UE, poids]) => {
return ` return `
<div>Poids ${UE}</div> <div>Poids ${UE}</div>
<div>${poids}</div> <div>${poids}</div>
`; `;
}).join("")} }).join("")}
</div> </div>
</div> </div>
`; `;
}) })
return output; return output;
} }
} }
/********************/ /********************/
/* Options */ /* Options */
/********************/ /********************/
function setOptions(options) { function setOptions(options) {
Object.entries(options).forEach(([option, value]) => { Object.entries(options).forEach(([option, value]) => {
if (value === false) { if (value === false) {
document.body.classList.add(option.replace("show", "hide")) document.body.classList.add(option.replace("show", "hide"))
} }
}) })
} }
@ -232,13 +232,13 @@ function setOptions(options) {
/* Fonctions d'aide */ /* Fonctions d'aide */
/********************/ /********************/
function civilite(txt) { function civilite(txt) {
switch (txt) { switch (txt) {
case "M": return "M."; case "M": return "M.";
case "F": return "Mme"; case "F": return "Mme";
default: return ""; default: return "";
} }
} }
function ISOToDate(ISO) { function ISOToDate(ISO) {
return ISO.split("-").reverse().join("/"); return ISO.split("-").reverse().join("/");
} }

View File

@ -94,9 +94,7 @@ def test_ue_moy(test_client):
# EXC à un module # EXC à un module
n1, n2 = 5.0, NOTES_NEUTRALISE n1, n2 = 5.0, NOTES_NEUTRALISE
etud_moy_ue = change_notes(n1, n2) etud_moy_ue = change_notes(n1, n2)
# Pour le moment, une note NEUTRALISE va entrainer le non-calcul assert (etud_moy_ue.values == n1).all()
# des moyennes.
assert np.isnan(etud_moy_ue.values).all()
# Désinscrit l'étudiant du module 2: # Désinscrit l'étudiant du module 2:
inscr = ModuleImplInscription.query.filter_by( inscr = ModuleImplInscription.query.filter_by(
moduleimpl_id=evaluation2.moduleimpl.id, etudid=etudid moduleimpl_id=evaluation2.moduleimpl.id, etudid=etudid