2021-12-05 20:21:51 +01:00
|
|
|
##############################################################################
|
|
|
|
# ScoDoc
|
2022-01-01 14:49:42 +01:00
|
|
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
2021-12-05 20:21:51 +01:00
|
|
|
# See LICENSE
|
|
|
|
##############################################################################
|
|
|
|
|
2021-12-26 19:15:47 +01:00
|
|
|
"""Génération bulletin BUT
|
|
|
|
"""
|
|
|
|
|
2021-12-05 20:21:51 +01:00
|
|
|
import datetime
|
2021-12-06 22:37:49 +01:00
|
|
|
from flask import url_for, g
|
2021-12-05 20:21:51 +01:00
|
|
|
|
2022-02-14 23:21:42 +01:00
|
|
|
from app.comp.res_but import ResultatsSemestreBUT
|
|
|
|
from app.models import FormSemestre, Identite
|
2021-12-05 20:21:51 +01:00
|
|
|
from app.scodoc import sco_utils as scu
|
2021-12-17 00:25:45 +01:00
|
|
|
from app.scodoc import sco_bulletins_json
|
2022-02-14 23:21:42 +01:00
|
|
|
from app.scodoc import sco_bulletins_pdf
|
2021-12-05 21:54:04 +01:00
|
|
|
from app.scodoc import sco_preferences
|
2022-01-25 10:45:13 +01:00
|
|
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
2021-12-26 19:15:47 +01:00
|
|
|
from app.scodoc.sco_utils import fmt_note
|
2021-12-05 20:21:51 +01:00
|
|
|
|
2021-12-24 00:08:25 +01:00
|
|
|
|
2022-02-09 23:22:00 +01:00
|
|
|
class BulletinBUT:
|
2021-12-24 00:08:25 +01:00
|
|
|
"""Génération du bulletin BUT.
|
|
|
|
Cette classe génère des dictionnaires avec toutes les informations
|
|
|
|
du bulletin, qui sont immédiatement traduisibles en JSON.
|
|
|
|
"""
|
|
|
|
|
2022-02-09 23:22:00 +01:00
|
|
|
def __init__(self, formsemestre: FormSemestre):
|
|
|
|
""" """
|
|
|
|
self.res = ResultatsSemestreBUT(formsemestre)
|
2022-02-14 23:21:42 +01:00
|
|
|
self.prefs = sco_preferences.SemPreferences(formsemestre.id)
|
2022-02-09 23:22:00 +01:00
|
|
|
|
2021-12-05 20:21:51 +01:00
|
|
|
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
|
|
|
|
"dict synthèse résultats dans l'UE pour les modules indiqués"
|
2022-02-09 23:22:00 +01:00
|
|
|
res = self.res
|
2021-12-05 20:21:51 +01:00
|
|
|
d = {}
|
2022-02-09 23:22:00 +01:00
|
|
|
etud_idx = res.etud_index[etud.id]
|
2022-01-29 22:45:39 +01:00
|
|
|
if ue.type != UE_SPORT:
|
2022-02-09 23:22:00 +01:00
|
|
|
ue_idx = res.modimpl_coefs_df.index.get_loc(ue.id)
|
|
|
|
etud_moy_module = res.sem_cube[etud_idx] # module x UE
|
2021-12-26 19:15:47 +01:00
|
|
|
for modimpl in modimpls:
|
2022-02-09 23:22:00 +01:00
|
|
|
if res.modimpl_inscr_df[modimpl.id][etud.id]: # si inscrit
|
2022-01-29 22:45:39 +01:00
|
|
|
if ue.type != UE_SPORT:
|
2022-02-09 23:22:00 +01:00
|
|
|
coef = res.modimpl_coefs_df[modimpl.id][ue.id]
|
2022-01-29 22:45:39 +01:00
|
|
|
if coef > 0:
|
|
|
|
d[modimpl.module.code] = {
|
|
|
|
"id": modimpl.id,
|
|
|
|
"coef": coef,
|
|
|
|
"moyenne": fmt_note(
|
|
|
|
etud_moy_module[
|
2022-02-09 23:22:00 +01:00
|
|
|
res.modimpl_coefs_df.columns.get_loc(modimpl.id)
|
2022-01-29 22:45:39 +01:00
|
|
|
][ue_idx]
|
|
|
|
),
|
|
|
|
}
|
|
|
|
# else: # modules dans UE bonus sport
|
|
|
|
# d[modimpl.module.code] = {
|
|
|
|
# "id": modimpl.id,
|
|
|
|
# "coef": "",
|
|
|
|
# "moyenne": "?x?",
|
|
|
|
# }
|
2021-12-05 20:21:51 +01:00
|
|
|
return d
|
|
|
|
|
|
|
|
def etud_ue_results(self, etud, ue):
|
|
|
|
"dict synthèse résultats UE"
|
2022-02-09 23:22:00 +01:00
|
|
|
res = self.res
|
2021-12-05 20:21:51 +01:00
|
|
|
d = {
|
|
|
|
"id": ue.id,
|
2022-01-29 22:59:40 +01:00
|
|
|
"titre": ue.titre,
|
2021-12-10 01:54:11 +01:00
|
|
|
"numero": ue.numero,
|
2022-01-25 10:45:13 +01:00
|
|
|
"type": ue.type,
|
2021-12-05 20:21:51 +01:00
|
|
|
"ECTS": {
|
2022-02-09 23:22:00 +01:00
|
|
|
"acquis": 0, # XXX TODO voir jury #sco92
|
2021-12-05 20:21:51 +01:00
|
|
|
"total": ue.ects,
|
|
|
|
},
|
2022-01-25 10:45:13 +01:00
|
|
|
"color": ue.color,
|
2021-12-05 20:21:51 +01:00
|
|
|
"competence": None, # XXX TODO lien avec référentiel
|
2022-01-25 10:45:13 +01:00
|
|
|
"moyenne": None,
|
|
|
|
# Le bonus sport appliqué sur cette UE
|
2022-02-09 23:22:00 +01:00
|
|
|
"bonus": fmt_note(res.bonus_ues[ue.id][etud.id])
|
|
|
|
if res.bonus_ues is not None and ue.id in res.bonus_ues
|
2022-01-26 11:51:13 +01:00
|
|
|
else fmt_note(0.0),
|
2022-02-09 23:22:00 +01:00
|
|
|
"malus": res.malus[ue.id][etud.id],
|
|
|
|
"capitalise": None, # "AAAA-MM-JJ" TODO #sco92
|
|
|
|
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
|
|
|
|
"saes": self.etud_ue_mod_results(etud, ue, res.saes),
|
2021-12-05 20:21:51 +01:00
|
|
|
}
|
2022-01-25 10:45:13 +01:00
|
|
|
if ue.type != UE_SPORT:
|
2022-02-14 23:21:42 +01:00
|
|
|
if self.prefs["bul_show_ue_rangs"]:
|
2022-02-09 23:22:00 +01:00
|
|
|
rangs, effectif = res.ue_rangs[ue.id]
|
2022-02-06 18:40:00 +01:00
|
|
|
rang = rangs[etud.id]
|
|
|
|
else:
|
|
|
|
rang, effectif = "", 0
|
2022-01-25 10:45:13 +01:00
|
|
|
d["moyenne"] = {
|
2022-02-09 23:22:00 +01:00
|
|
|
"value": fmt_note(res.etud_moy_ue[ue.id][etud.id]),
|
|
|
|
"min": fmt_note(res.etud_moy_ue[ue.id].min()),
|
|
|
|
"max": fmt_note(res.etud_moy_ue[ue.id].max()),
|
|
|
|
"moy": fmt_note(res.etud_moy_ue[ue.id].mean()),
|
2022-02-06 18:40:00 +01:00
|
|
|
"rang": rang,
|
2022-02-06 18:29:22 +01:00
|
|
|
"total": effectif, # nb etud avec note dans cette UE
|
2022-01-25 10:45:13 +01:00
|
|
|
}
|
2022-01-26 11:51:13 +01:00
|
|
|
else:
|
|
|
|
# ceci suppose que l'on a une seule UE bonus,
|
|
|
|
# en tous cas elles auront la même description
|
|
|
|
d["bonus_description"] = self.etud_bonus_description(etud.id)
|
|
|
|
modimpls_spo = [
|
|
|
|
modimpl
|
2022-02-09 23:22:00 +01:00
|
|
|
for modimpl in res.formsemestre.modimpls_sorted
|
2022-01-26 11:51:13 +01:00
|
|
|
if modimpl.module.ue.type == UE_SPORT
|
|
|
|
]
|
|
|
|
d["modules"] = self.etud_mods_results(etud, modimpls_spo)
|
2021-12-05 20:21:51 +01:00
|
|
|
return d
|
|
|
|
|
|
|
|
def etud_mods_results(self, etud, modimpls) -> dict:
|
|
|
|
"""dict synthèse résultats des modules indiqués,
|
|
|
|
avec évaluations de chacun."""
|
2022-02-09 23:22:00 +01:00
|
|
|
res = self.res
|
2021-12-05 20:21:51 +01:00
|
|
|
d = {}
|
2021-12-18 12:16:49 +01:00
|
|
|
# etud_idx = self.etud_index[etud.id]
|
2021-12-26 19:15:47 +01:00
|
|
|
for modimpl in modimpls:
|
2021-12-18 12:16:49 +01:00
|
|
|
# mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
|
2021-12-16 00:03:24 +01:00
|
|
|
# # moyennes indicatives (moyennes de moyennes d'UE)
|
|
|
|
# try:
|
|
|
|
# moyennes_etuds = np.nan_to_num(
|
|
|
|
# np.nanmean(self.sem_cube[:, mod_idx, :], axis=1),
|
|
|
|
# copy=False,
|
|
|
|
# )
|
2022-02-10 14:34:16 +01:00
|
|
|
# except RuntimeWarning:
|
|
|
|
# # all nans in np.nanmean (sur certains etuds sans notes valides)
|
2021-12-16 00:03:24 +01:00
|
|
|
# pass
|
|
|
|
# try:
|
|
|
|
# moy_indicative_mod = np.nanmean(self.sem_cube[etud_idx, mod_idx])
|
|
|
|
# except RuntimeWarning: # all nans in np.nanmean
|
|
|
|
# pass
|
2022-02-09 23:22:00 +01:00
|
|
|
modimpl_results = res.modimpls_results[modimpl.id]
|
|
|
|
if res.modimpl_inscr_df[modimpl.id][etud.id]: # si inscrit
|
2022-01-09 22:33:08 +01:00
|
|
|
d[modimpl.module.code] = {
|
|
|
|
"id": modimpl.id,
|
|
|
|
"titre": modimpl.module.titre,
|
|
|
|
"code_apogee": modimpl.module.code_apogee,
|
2022-01-09 21:48:58 +01:00
|
|
|
"url": url_for(
|
|
|
|
"notes.moduleimpl_status",
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
2022-01-09 22:33:08 +01:00
|
|
|
moduleimpl_id=modimpl.id,
|
2022-01-09 21:48:58 +01:00
|
|
|
),
|
|
|
|
"moyenne": {
|
2022-02-10 14:34:16 +01:00
|
|
|
# # moyenne indicative de module: moyenne des UE,
|
|
|
|
# # ignorant celles sans notes (nan)
|
2022-01-09 21:48:58 +01:00
|
|
|
# "value": fmt_note(moy_indicative_mod),
|
|
|
|
# "min": fmt_note(moyennes_etuds.min()),
|
|
|
|
# "max": fmt_note(moyennes_etuds.max()),
|
|
|
|
# "moy": fmt_note(moyennes_etuds.mean()),
|
|
|
|
},
|
|
|
|
"evaluations": [
|
|
|
|
self.etud_eval_results(etud, e)
|
2022-01-09 22:33:08 +01:00
|
|
|
for e in modimpl.evaluations
|
2022-01-09 21:48:58 +01:00
|
|
|
if e.visibulletin
|
2022-02-10 18:49:25 +01:00
|
|
|
and (
|
|
|
|
modimpl_results.evaluations_etat[e.id].is_complete
|
2022-02-14 23:21:42 +01:00
|
|
|
or self.prefs["bul_show_all_evals"]
|
2022-02-10 18:49:25 +01:00
|
|
|
)
|
2022-01-09 21:48:58 +01:00
|
|
|
],
|
|
|
|
}
|
2021-12-05 20:21:51 +01:00
|
|
|
return d
|
|
|
|
|
|
|
|
def etud_eval_results(self, etud, e) -> dict:
|
|
|
|
"dict resultats d'un étudiant à une évaluation"
|
2021-12-26 19:15:47 +01:00
|
|
|
# eval_notes est une pd.Series avec toutes les notes des étudiants inscrits
|
2022-02-09 23:22:00 +01:00
|
|
|
eval_notes = self.res.modimpls_results[e.moduleimpl_id].evals_notes[e.id]
|
2021-12-11 10:56:40 +01:00
|
|
|
notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
|
2021-12-05 20:21:51 +01:00
|
|
|
d = {
|
|
|
|
"id": e.id,
|
|
|
|
"description": e.description,
|
2021-12-08 14:13:18 +01:00
|
|
|
"date": e.jour.isoformat() if e.jour else None,
|
2021-12-05 20:21:51 +01:00
|
|
|
"heure_debut": e.heure_debut.strftime("%H:%M") if e.heure_debut else None,
|
|
|
|
"heure_fin": e.heure_fin.strftime("%H:%M") if e.heure_debut else None,
|
|
|
|
"coef": e.coefficient,
|
|
|
|
"poids": {p.ue.acronyme: p.poids for p in e.ue_poids},
|
|
|
|
"note": {
|
2021-12-06 10:57:10 +01:00
|
|
|
"value": fmt_note(
|
2021-12-26 19:15:47 +01:00
|
|
|
eval_notes[etud.id],
|
2021-12-16 21:10:20 +01:00
|
|
|
note_max=e.note_max,
|
2021-12-05 20:21:51 +01:00
|
|
|
),
|
2021-12-06 10:57:10 +01:00
|
|
|
"min": fmt_note(notes_ok.min()),
|
|
|
|
"max": fmt_note(notes_ok.max()),
|
|
|
|
"moy": fmt_note(notes_ok.mean()),
|
2021-12-05 20:21:51 +01:00
|
|
|
},
|
2021-12-12 08:44:05 +01:00
|
|
|
"url": url_for(
|
|
|
|
"notes.evaluation_listenotes",
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
evaluation_id=e.id,
|
|
|
|
),
|
2021-12-05 20:21:51 +01:00
|
|
|
}
|
|
|
|
return d
|
|
|
|
|
2022-01-26 11:51:13 +01:00
|
|
|
def etud_bonus_description(self, etudid):
|
|
|
|
"""description du bonus affichée dans la section "UE bonus"."""
|
2022-02-09 23:22:00 +01:00
|
|
|
res = self.res
|
|
|
|
if res.bonus_ues is None or res.bonus_ues.shape[1] == 0:
|
2022-01-26 11:51:13 +01:00
|
|
|
return ""
|
|
|
|
|
2022-02-09 23:22:00 +01:00
|
|
|
bonus_vect = res.bonus_ues.loc[etudid]
|
2022-01-26 11:51:13 +01:00
|
|
|
if bonus_vect.nunique() > 1:
|
|
|
|
# détail UE par UE
|
|
|
|
details = [
|
|
|
|
f"{fmt_note(bonus_vect[ue.id])} sur {ue.acronyme}"
|
2022-02-09 23:22:00 +01:00
|
|
|
for ue in res.ues
|
|
|
|
if res.modimpls_in_ue(ue.id, etudid)
|
|
|
|
and ue.id in res.bonus_ues
|
2022-01-29 23:36:07 +01:00
|
|
|
and bonus_vect[ue.id] > 0.0
|
2022-01-26 11:51:13 +01:00
|
|
|
]
|
|
|
|
if details:
|
|
|
|
return "Bonus de " + ", ".join(details)
|
|
|
|
else:
|
|
|
|
return "" # aucun bonus
|
|
|
|
else:
|
|
|
|
return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"
|
|
|
|
|
2022-02-14 23:21:42 +01:00
|
|
|
def bulletin_etud(
|
|
|
|
self, etud: Identite, formsemestre, force_publishing=False
|
|
|
|
) -> dict:
|
|
|
|
"""Le bulletin de l'étudiant dans ce semestre: dict pour la version JSON / HTML.
|
|
|
|
- Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
2022-01-25 13:24:08 +01:00
|
|
|
(bulletins non publiés).
|
|
|
|
"""
|
2022-02-09 23:22:00 +01:00
|
|
|
res = self.res
|
2021-12-16 12:41:37 +01:00
|
|
|
etat_inscription = etud.etat_inscription(formsemestre.id)
|
2022-02-10 14:34:16 +01:00
|
|
|
nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
|
2022-01-25 13:24:08 +01:00
|
|
|
published = (not formsemestre.bul_hide_xml) or force_publishing
|
2021-12-05 20:21:51 +01:00
|
|
|
d = {
|
|
|
|
"version": "0",
|
|
|
|
"type": "BUT",
|
|
|
|
"date": datetime.datetime.utcnow().isoformat() + "Z",
|
2022-01-25 13:24:08 +01:00
|
|
|
"publie": not formsemestre.bul_hide_xml,
|
2021-12-05 20:21:51 +01:00
|
|
|
"etudiant": etud.to_dict_bul(),
|
|
|
|
"formation": {
|
|
|
|
"id": formsemestre.formation.id,
|
|
|
|
"acronyme": formsemestre.formation.acronyme,
|
|
|
|
"titre_officiel": formsemestre.formation.titre_officiel,
|
|
|
|
"titre": formsemestre.formation.titre,
|
|
|
|
},
|
|
|
|
"formsemestre_id": formsemestre.id,
|
2021-12-16 12:41:37 +01:00
|
|
|
"etat_inscription": etat_inscription,
|
2022-02-14 23:21:42 +01:00
|
|
|
"options": sco_preferences.bulletin_option_affichage(
|
|
|
|
formsemestre.id, self.prefs
|
|
|
|
),
|
2021-12-05 20:21:51 +01:00
|
|
|
}
|
2022-01-25 13:24:08 +01:00
|
|
|
if not published:
|
|
|
|
return d
|
2022-01-26 00:11:04 +01:00
|
|
|
|
|
|
|
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
|
2021-12-16 12:41:37 +01:00
|
|
|
semestre_infos = {
|
2021-12-17 00:25:45 +01:00
|
|
|
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
|
2021-12-16 12:41:37 +01:00
|
|
|
"date_debut": formsemestre.date_debut.isoformat(),
|
|
|
|
"date_fin": formsemestre.date_fin.isoformat(),
|
2022-02-09 23:22:00 +01:00
|
|
|
"annee_universitaire": formsemestre.annee_scolaire_str(),
|
2021-12-16 12:41:37 +01:00
|
|
|
"numero": formsemestre.semestre_id,
|
2022-02-02 22:22:56 +01:00
|
|
|
"inscription": "", # inutilisé mais nécessaire pour le js de Seb.
|
2021-12-16 12:41:37 +01:00
|
|
|
"groupes": [], # XXX TODO
|
2022-01-26 00:11:04 +01:00
|
|
|
"absences": {
|
2022-02-14 09:22:48 +01:00
|
|
|
"injustifie": nbabs - nbabsjust,
|
2022-01-26 00:11:04 +01:00
|
|
|
"total": nbabs,
|
2021-12-16 12:41:37 +01:00
|
|
|
},
|
|
|
|
}
|
2021-12-17 00:25:45 +01:00
|
|
|
semestre_infos.update(
|
|
|
|
sco_bulletins_json.dict_decision_jury(etud.id, formsemestre.id)
|
|
|
|
)
|
2021-12-16 12:41:37 +01:00
|
|
|
if etat_inscription == scu.INSCRIT:
|
|
|
|
semestre_infos.update(
|
|
|
|
{
|
|
|
|
"notes": { # moyenne des moyennes générales du semestre
|
2022-02-09 23:22:00 +01:00
|
|
|
"value": fmt_note(res.etud_moy_gen[etud.id]),
|
|
|
|
"min": fmt_note(res.etud_moy_gen.min()),
|
|
|
|
"moy": fmt_note(res.etud_moy_gen.mean()),
|
|
|
|
"max": fmt_note(res.etud_moy_gen.max()),
|
2021-12-16 12:41:37 +01:00
|
|
|
},
|
|
|
|
"rang": { # classement wrt moyenne général, indicatif
|
2022-02-09 23:22:00 +01:00
|
|
|
"value": res.etud_moy_gen_ranks[etud.id],
|
2022-01-16 23:47:52 +01:00
|
|
|
"total": nb_inscrits,
|
2021-12-16 12:41:37 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
d.update(
|
|
|
|
{
|
2022-02-09 23:22:00 +01:00
|
|
|
"ressources": self.etud_mods_results(etud, res.ressources),
|
|
|
|
"saes": self.etud_mods_results(etud, res.saes),
|
2021-12-16 12:41:37 +01:00
|
|
|
"ues": {
|
2022-01-29 23:36:07 +01:00
|
|
|
ue.acronyme: self.etud_ue_results(etud, ue)
|
2022-02-09 23:22:00 +01:00
|
|
|
for ue in res.ues
|
2022-02-10 14:34:16 +01:00
|
|
|
if self.res.modimpls_in_ue(
|
2022-01-29 23:36:07 +01:00
|
|
|
ue.id, etud.id
|
|
|
|
) # si l'UE comporte des modules auxquels on est inscrit
|
2021-12-16 12:41:37 +01:00
|
|
|
},
|
|
|
|
"semestre": semestre_infos,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
semestre_infos.update(
|
|
|
|
{
|
|
|
|
"notes": {
|
|
|
|
"value": "DEM",
|
|
|
|
"min": "",
|
|
|
|
"moy": "",
|
|
|
|
"max": "",
|
|
|
|
},
|
2022-01-16 23:47:52 +01:00
|
|
|
"rang": {"value": "DEM", "total": nb_inscrits},
|
2021-12-16 12:41:37 +01:00
|
|
|
}
|
|
|
|
)
|
|
|
|
d.update(
|
|
|
|
{
|
|
|
|
"semestre": semestre_infos,
|
|
|
|
"ressources": {},
|
|
|
|
"saes": {},
|
|
|
|
"ues": {},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2021-12-05 20:21:51 +01:00
|
|
|
return d
|
2022-02-14 23:21:42 +01:00
|
|
|
|
|
|
|
def bulletin_etud_complet(self, etud) -> dict:
|
|
|
|
"""Bulletin dict complet avec toutes les infos pour les bulletins pdf"""
|
|
|
|
d = self.bulletin_etud(force_publishing=True)
|
|
|
|
d["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
|
|
|
self.res.get_etud_etat(etud.id), self.prefs
|
|
|
|
)
|
|
|
|
# XXX TODO A COMPLETER
|
|
|
|
raise NotImplementedError()
|