diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py
index 4ff2849f..65756767 100644
--- a/app/but/bulletin_but.py
+++ b/app/but/bulletin_but.py
@@ -4,7 +4,6 @@
# See LICENSE
##############################################################################
-from collections import defaultdict
import datetime
from flask import url_for, g
import numpy as np
@@ -15,62 +14,24 @@ from app import db
from app.comp import moy_ue, moy_sem, inscr_mod
from app.models import ModuleImpl
from app.scodoc import sco_utils as scu
-from app.scodoc.sco_cache import ResultatsSemestreBUTCache
from app.scodoc import sco_bulletins_json
from app.scodoc import sco_preferences
from app.scodoc.sco_utils import jsnan, fmt_note
+from app.comp.res_sem import ResultatsSemestre, NotesTableCompat
-class ResultatsSemestreBUT:
- """Structure légère pour stocker les résultats du semestre et
- générer les bulletins.
- __init__ : charge depuis le cache ou calcule
- """
+class ResultatsSemestreBUT(NotesTableCompat):
+ """Résultats BUT: organisation des calculs"""
- _cached_attrs = (
- "sem_cube",
- "modimpl_inscr_df",
- "modimpl_coefs_df",
- "etud_moy_ue",
- "modimpls_evals_poids",
- "modimpls_evals_notes",
- "etud_moy_gen",
- "etud_moy_gen_ranks",
- "modimpls_evaluations_complete",
- )
+ _cached_attrs = NotesTableCompat._cached_attrs + ()
def __init__(self, formsemestre):
- self.formsemestre = formsemestre
- self.ues = formsemestre.query_ues().all()
- self.modimpls = formsemestre.modimpls.all()
- self.etuds = self.formsemestre.get_inscrits(include_dem=False)
- self.etud_index = {e.id: idx for idx, e in enumerate(self.etuds)}
- self.saes = [
- m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE
- ]
- self.ressources = [
- m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
- ]
+ super().__init__(formsemestre)
+
if not self.load_cached():
self.compute()
self.store()
- def load_cached(self) -> bool:
- "Load cached dataframes, returns False si pas en cache"
- data = ResultatsSemestreBUTCache.get(self.formsemestre.id)
- if not data:
- return False
- for attr in self._cached_attrs:
- setattr(self, attr, data[attr])
- return True
-
- def store(self):
- "Cache our dataframes"
- ResultatsSemestreBUTCache.set(
- self.formsemestre.id,
- {attr: getattr(self, attr) for attr in self._cached_attrs},
- )
-
def compute(self):
"Charge les notes et inscriptions et calcule toutes les moyennes"
(
@@ -100,6 +61,13 @@ class ResultatsSemestreBUT:
)
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
+
+class BulletinBUT(ResultatsSemestreBUT):
+ """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.
+ """
+
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
"dict synthèse résultats dans l'UE pour les modules indiqués"
d = {}
@@ -233,7 +201,7 @@ class ResultatsSemestreBUT:
},
"formsemestre_id": formsemestre.id,
"etat_inscription": etat_inscription,
- "options": bulletin_option_affichage(formsemestre),
+ "options": sco_preferences.bulletin_option_affichage(formsemestre.id),
}
semestre_infos = {
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
@@ -298,125 +266,3 @@ class ResultatsSemestreBUT:
)
return d
-
-
-def bulletin_option_affichage(formsemestre):
- "dict avec les options d'affichages (préférences) pour ce semestre"
- prefs = sco_preferences.SemPreferences(formsemestre.id)
- fields = (
- "bul_show_abs",
- "bul_show_abs_modules",
- "bul_show_ects",
- "bul_show_codemodules",
- "bul_show_matieres",
- "bul_show_rangs",
- "bul_show_ue_rangs",
- "bul_show_mod_rangs",
- "bul_show_moypromo",
- "bul_show_minmax",
- "bul_show_minmax_mod",
- "bul_show_minmax_eval",
- "bul_show_coef",
- "bul_show_ue_cap_details",
- "bul_show_ue_cap_current",
- "bul_show_temporary",
- "bul_temporary_txt",
- "bul_show_uevalid",
- "bul_show_date_inscr",
- )
- # on enlève le "bul_" de la clé:
- return {field[4:]: prefs[field] for field in fields}
-
-
-# Pour raccorder le code des anciens bulletins qui attendent une NoteTable
-class APCNotesTableCompat:
- """Implementation partielle de NotesTable pour les formations APC
- Accès aux notes et rangs.
- """
-
- def __init__(self, formsemestre):
- self.results = ResultatsSemestreBUT(formsemestre)
- nb_etuds = len(self.results.etuds)
- self.rangs = self.results.etud_moy_gen_ranks
- self.moy_min = self.results.etud_moy_gen.min()
- self.moy_max = self.results.etud_moy_gen.max()
- self.moy_moy = self.results.etud_moy_gen.mean()
- self.bonus = defaultdict(lambda: 0.0) # XXX
- self.ue_rangs = {
- u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.results.ues
- }
- self.mod_rangs = {
- m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.results.modimpls
- }
-
- def get_ues(self):
- ues = []
- for ue in self.results.ues:
- d = ue.to_dict()
- d.update(
- {
- "max": self.results.etud_moy_ue[ue.id].max(),
- "min": self.results.etud_moy_ue[ue.id].min(),
- "moy": self.results.etud_moy_ue[ue.id].mean(),
- "nb_moy": len(self.results.etud_moy_ue),
- }
- )
- ues.append(d)
- return ues
-
- def get_modimpls(self):
- return [m.to_dict() for m in self.results.modimpls]
-
- def get_etud_moy_gen(self, etudid):
- return self.results.etud_moy_gen[etudid]
-
- def get_moduleimpls_attente(self):
- return [] # XXX TODO
-
- def get_etud_rang(self, etudid):
- return self.rangs[etudid]
-
- def get_etud_rang_group(self, etudid, group_id):
- return (None, 0) # XXX unimplemented TODO
-
- def get_etud_ue_status(self, etudid, ue_id):
- return {
- "cur_moy_ue": self.results.etud_moy_ue[ue_id][etudid],
- "is_capitalized": False, # XXX TODO
- }
-
- def get_etud_mod_moy(self, moduleimpl_id, etudid):
- mod_idx = self.results.modimpl_coefs_df.columns.get_loc(moduleimpl_id)
- etud_idx = self.results.etud_index[etudid]
- # moyenne sur les UE:
- self.results.sem_cube[etud_idx, mod_idx].mean()
-
- def get_mod_stats(self, moduleimpl_id):
- return {
- "moy": "-",
- "max": "-",
- "min": "-",
- "nb_notes": "-",
- "nb_missing": "-",
- "nb_valid_evals": "-",
- }
-
- def get_evals_in_mod(self, moduleimpl_id):
- mi = ModuleImpl.query.get(moduleimpl_id)
- evals_results = []
- for e in mi.evaluations:
- d = e.to_dict()
- d["heure_debut"] = e.heure_debut # datetime.time
- d["heure_fin"] = e.heure_fin
- d["jour"] = e.jour # datetime
- d["notes"] = {
- etud.id: {
- "etudid": etud.id,
- "value": self.results.modimpls_evals_notes[e.moduleimpl_id][e.id][
- etud.id
- ],
- }
- for etud in self.results.etuds
- }
- evals_results.append(d)
- return evals_results
diff --git a/app/but/bulletin_but_xml_compat.py b/app/but/bulletin_but_xml_compat.py
index 9e234dc0..6e2f14db 100644
--- a/app/but/bulletin_but_xml_compat.py
+++ b/app/but/bulletin_but_xml_compat.py
@@ -314,13 +314,3 @@ def bulletin_but_xml_compat(
return None
else:
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
-
-
-"""
-formsemestre_id=718
-etudid=12496
-from app.but.bulletin_but import *
-mapp.set_sco_dept("RT")
-sem = FormSemestre.query.get(formsemestre_id)
-r = ResultatsSemestreBUT(sem)
-"""
diff --git a/app/comp/res_sem.py b/app/comp/res_sem.py
new file mode 100644
index 00000000..01a2e872
--- /dev/null
+++ b/app/comp/res_sem.py
@@ -0,0 +1,216 @@
+##############################################################################
+# ScoDoc
+# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
+# See LICENSE
+##############################################################################
+
+from collections import defaultdict
+from functools import cached_property
+import numpy as np
+import pandas as pd
+from app.scodoc import sco_utils as scu
+from app.scodoc.sco_cache import ResultatsSemestreCache
+from app.scodoc.sco_codes_parcours import UE_SPORT
+
+# Il faut bien distinguer
+# - ce qui est caché de façon persistente (via redis):
+# ce sont les attributs listés dans `_cached_attrs`
+# le stockage et l'invalidation sont gérés dans sco_cache.py
+#
+# - les valeurs cachées durant le temps d'une requête
+# (durée de vie de l'instance de ResultatsSemestre)
+# qui sont notamment les attributs décorés par `@cached_property``
+#
+class ResultatsSemestre:
+ _cached_attrs = (
+ "sem_cube",
+ "modimpl_inscr_df",
+ "modimpl_coefs_df",
+ "etud_moy_ue",
+ "modimpls_evals_poids",
+ "modimpls_evals_notes",
+ "etud_moy_gen",
+ "etud_moy_gen_ranks",
+ "modimpls_evaluations_complete",
+ )
+
+ def __init__(self, formsemestre):
+ self.formsemestre = formsemestre
+ # TODO
+
+ def load_cached(self) -> bool:
+ "Load cached dataframes, returns False si pas en cache"
+ data = ResultatsSemestreCache.get(self.formsemestre.id)
+ if not data:
+ return False
+ for attr in self._cached_attrs:
+ setattr(self, attr, data[attr])
+ return True
+
+ def store(self):
+ "Cache our data"
+ "Cache our dataframes"
+ ResultatsSemestreCache.set(
+ self.formsemestre.id,
+ {attr: getattr(self, attr) for attr in self._cached_attrs},
+ )
+
+ def compute(self):
+ "Charge les notes et inscriptions et calcule toutes les moyennes"
+ # voir ce qui est chargé / calculé ici et dans les sous-classes
+ TODO
+
+ @cached_property
+ def etuds(self):
+ "Liste des inscrits au semestre, sans les démissionnaires"
+ # nb: si les liste des inscrits change, ResultatsSemestre devient invalide
+ return self.formsemestre.get_inscrits(include_dem=False)
+
+ @cached_property
+ def etud_index(self):
+ "dict { etudid : indice dans les inscrits }"
+ return {e.id: idx for idx, e in enumerate(self.etuds)}
+
+ @cached_property
+ def ues(self):
+ "Liste des UE du semestre"
+ return self.formsemestre.query_ues().all()
+
+ @cached_property
+ def modimpls(self):
+ "Liste des modimpls du semestre (triée par numéro de module)"
+ modimpls = self.formsemestre.modimpls.all()
+ modimpls.sort(key=lambda m: m.module.numero)
+ return modimpls
+
+ @cached_property
+ def ressources(self):
+ "Liste des ressources du semestre, triées par numéro de module"
+ return [
+ m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
+ ]
+
+ @cached_property
+ def saes(self):
+ "Liste des SAÉs du semestre, triées par numéro de module"
+ return [m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE]
+
+
+class StatsMoyenne:
+ """Une moyenne d'un ensemble étudiants sur quelque chose
+ (moyenne générale d'un semestre, d'un module, d'un groupe...)
+ et les statistiques associées: min, max, moy, effectif
+ """
+
+ def __init__(self, vals):
+ """Calcul les statistiques.
+ Les valeurs NAN ou non numériques sont toujours enlevées.
+ """
+ self.moy = np.nanmean(vals)
+ self.min = np.nanmin(vals)
+ self.max = np.nanmax(vals)
+ self.size = len(vals)
+ self.nb_vals = self.size - np.count_nonzero(np.isnan(vals))
+
+ def to_dict(self):
+ return {
+ "min": self.min,
+ "max": self.max,
+ "moy": self.moy,
+ "size": self.size,
+ "nb_vals": self.nb_vals,
+ }
+
+
+# Pour raccorder le code des anciens codes qui attendent une NoteTable
+class NotesTableCompat(ResultatsSemestre):
+ """Implementation partielle de NotesTable WIP TODO
+ Accès aux notes et rangs.
+ """
+
+ _cached_attrs = ResultatsSemestre._cached_attrs + ()
+
+ def __init__(self, formsemestre):
+ super().__init__(formsemestre)
+ nb_etuds = len(self.etuds)
+ self.bonus = defaultdict(lambda: 0.0) # XXX TODO
+ self.ue_rangs = {u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.ues}
+ self.mod_rangs = {
+ m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.modimpls
+ }
+
+ @cached_property
+ def stats_moy_gen(self):
+ """Stats (moy/min/max) sur la moyenne générale"""
+ return StatsMoyenne(self.etud_moy_gen)
+
+ def get_ues_stat_dict(self, filter_sport=False): # was get_ues()
+ """Liste des UEs, ordonnée par numero.
+ Si filter_sport, retire les UE de type SPORT.
+ Résultat: liste de dicts { champs UE U stats moyenne UE }
+ """
+ ues = []
+ for ue in self.ues:
+ if filter_sport and ue.type == UE_SPORT:
+ continue
+ d = ue.to_dict()
+ d.update(StatsMoyenne(self.etud_moy_ue[ue.id]).to_dict())
+ ues.append(d)
+ return ues
+
+ def get_modimpls(self):
+ return [m.to_dict() for m in self.results.modimpls]
+
+ def get_etud_moy_gen(self, etudid):
+ return self.results.etud_moy_gen[etudid]
+
+ def get_moduleimpls_attente(self):
+ return [] # XXX TODO
+
+ def get_etud_rang(self, etudid):
+ return self.etud_moy_gen_ranks[etudid]
+
+ def get_etud_rang_group(self, etudid, group_id):
+ return (None, 0) # XXX unimplemented TODO
+
+ def get_etud_ue_status(self, etudid, ue_id):
+ return {
+ "cur_moy_ue": self.results.etud_moy_ue[ue_id][etudid],
+ "is_capitalized": False, # XXX TODO
+ }
+
+ def get_etud_mod_moy(self, moduleimpl_id, etudid):
+ mod_idx = self.results.modimpl_coefs_df.columns.get_loc(moduleimpl_id)
+ etud_idx = self.results.etud_index[etudid]
+ # moyenne sur les UE:
+ self.results.sem_cube[etud_idx, mod_idx].mean()
+
+ def get_mod_stats(self, moduleimpl_id):
+ return {
+ "moy": "-",
+ "max": "-",
+ "min": "-",
+ "nb_notes": "-",
+ "nb_missing": "-",
+ "nb_valid_evals": "-",
+ }
+
+ def get_evals_in_mod(self, moduleimpl_id):
+ mi = ModuleImpl.query.get(moduleimpl_id)
+ evals_results = []
+ for e in mi.evaluations:
+ d = e.to_dict()
+ d["heure_debut"] = e.heure_debut # datetime.time
+ d["heure_fin"] = e.heure_fin
+ d["jour"] = e.jour # datetime
+ d["notes"] = {
+ etud.id: {
+ "etudid": etud.id,
+ "value": self.results.modimpls_evals_notes[e.moduleimpl_id][e.id][
+ etud.id
+ ],
+ }
+ for etud in self.results.etuds
+ }
+ evals_results.append(d)
+ return evals_results
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 450ea2cc..98edbba1 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -84,7 +84,11 @@ class FormSemestre(db.Model):
etapes = db.relationship(
"FormSemestreEtape", cascade="all,delete", backref="formsemestre"
)
- modimpls = db.relationship("ModuleImpl", backref="formsemestre", lazy="dynamic")
+ modimpls = db.relationship(
+ "ModuleImpl",
+ backref="formsemestre",
+ lazy="dynamic",
+ )
etuds = db.relationship(
"Identite",
secondary="notes_formsemestre_inscription",
diff --git a/app/pe/pe_tagtable.py b/app/pe/pe_tagtable.py
index e32a1173..caba1636 100644
--- a/app/pe/pe_tagtable.py
+++ b/app/pe/pe_tagtable.py
@@ -68,7 +68,7 @@ class TableTag(object):
self.taglist = []
self.resultats = {}
- self.rangs = {}
+ self.etud_moy_gen_ranks = {}
self.statistiques = {}
# *****************************************************************************************************************
@@ -117,15 +117,15 @@ class TableTag(object):
# -----------------------------------------------------------------------------------------------------------
def get_moy_from_stats(self, tag):
- """ Renvoie la moyenne des notes calculées pour d'un tag donné"""
+ """Renvoie la moyenne des notes calculées pour d'un tag donné"""
return self.statistiques[tag][0] if tag in self.statistiques else None
def get_min_from_stats(self, tag):
- """ Renvoie la plus basse des notes calculées pour d'un tag donné"""
+ """Renvoie la plus basse des notes calculées pour d'un tag donné"""
return self.statistiques[tag][1] if tag in self.statistiques else None
def get_max_from_stats(self, tag):
- """ Renvoie la plus haute des notes calculées pour d'un tag donné"""
+ """Renvoie la plus haute des notes calculées pour d'un tag donné"""
return self.statistiques[tag][2] if tag in self.statistiques else None
# -----------------------------------------------------------------------------------------------------------
@@ -236,7 +236,7 @@ class TableTag(object):
return moystr
def str_res_d_un_etudiant(self, etudid, delim=";"):
- """Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique). """
+ """Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique)."""
return delim.join(
[self.str_resTag_d_un_etudiant(tag, etudid) for tag in self.get_all_tags()]
)
@@ -256,7 +256,7 @@ class TableTag(object):
# -----------------------------------------------------------------------
def str_tagtable(self, delim=";", decimal_sep=","):
- """Renvoie une chaine de caractère listant toutes les moyennes, les rangs des étudiants pour tous les tags. """
+ """Renvoie une chaine de caractère listant toutes les moyennes, les rangs des étudiants pour tous les tags."""
entete = ["etudid", "nom", "prenom"]
for tag in self.get_all_tags():
entete += [titre + "_" + tag for titre in ["note", "rang", "nb_inscrit"]]
diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py
index 052b387e..69c37694 100644
--- a/app/scodoc/notes_table.py
+++ b/app/scodoc/notes_table.py
@@ -25,7 +25,9 @@
#
##############################################################################
-"""Calculs sur les notes et cache des resultats
+"""Calculs sur les notes et cache des résultats
+
+ Ancien code ScoDoc 7 en cours de rénovation
"""
from operator import itemgetter
@@ -102,7 +104,7 @@ def comp_ranks(T):
def get_sem_ues_modimpls(formsemestre_id, modimpls=None):
"""Get liste des UE du semestre (à partir des moduleimpls)
- (utilisé quand on ne peut pas construire nt et faire nt.get_ues())
+ (utilisé quand on ne peut pas construire nt et faire nt.get_ues_stat_dict())
"""
if modimpls is None:
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
@@ -316,7 +318,7 @@ class NotesTable:
self.moy_min = self.moy_max = "NA"
# calcul rangs (/ moyenne generale)
- self.rangs = comp_ranks(T)
+ self.etud_moy_gen_ranks = comp_ranks(T)
self.rangs_groupes = (
{}
@@ -417,43 +419,14 @@ class NotesTable:
else:
return ' (%s) ' % etat
- def get_ues(self, filter_sport=False, filter_non_inscrit=False, etudid=None):
- """liste des ue, ordonnée par numero.
- Si filter_non_inscrit, retire les UE dans lesquelles l'etudiant n'est
- inscrit à aucun module.
+ def get_ues_stat_dict(self, filter_sport=False): # was get_ues()
+ """Liste des UEs, ordonnée par numero.
Si filter_sport, retire les UE de type SPORT
"""
- if not filter_sport and not filter_non_inscrit:
+ if not filter_sport:
return self._ues
-
- if filter_sport:
- ues_src = [ue for ue in self._ues if ue["type"] != UE_SPORT]
else:
- ues_src = self._ues
- if not filter_non_inscrit:
- return ues_src
- ues = []
- for ue in ues_src:
- if self.get_etud_ue_status(etudid, ue["ue_id"])["is_capitalized"]:
- # garde toujours les UE capitalisees
- has_note = True
- else:
- has_note = False
- # verifie que l'etud. est inscrit a au moins un module de l'UE
- # (en fait verifie qu'il a une note)
- modimpls = self.get_modimpls(ue["ue_id"])
-
- for modi in modimpls:
- moy = self.get_etud_mod_moy(modi["moduleimpl_id"], etudid)
- try:
- float(moy)
- has_note = True
- break
- except:
- pass
- if has_note:
- ues.append(ue)
- return ues
+ return [ue for ue in self._ues if ue["type"] != UE_SPORT]
def get_modimpls(self, ue_id=None):
"liste des modules pour une UE (ou toutes si ue_id==None), triés par matières."
@@ -522,7 +495,7 @@ class NotesTable:
Les moyennes d'UE ne tiennent pas compte des capitalisations.
"""
- ues = self.get_ues()
+ ues = self.get_ues_stat_dict()
sum_moy = 0 # la somme des moyennes générales valides
nb_moy = 0 # le nombre de moyennes générales valides
for ue in ues:
@@ -561,9 +534,9 @@ class NotesTable:
i = 0
for ue in ues:
i += 1
- ue["nb_moy"] = len(ue["_notes"])
- if ue["nb_moy"] > 0:
- ue["moy"] = sum(ue["_notes"]) / ue["nb_moy"]
+ ue["nb_vals"] = len(ue["_notes"])
+ if ue["nb_vals"] > 0:
+ ue["moy"] = sum(ue["_notes"]) / ue["nb_vals"]
ue["max"] = max(ue["_notes"])
ue["min"] = min(ue["_notes"])
else:
@@ -767,7 +740,7 @@ class NotesTable:
sem_ects_pot_fond = 0.0
sem_ects_pot_pro = 0.0
- for ue in self.get_ues():
+ for ue in self.get_ues_stat_dict():
# - On calcule la moyenne d'UE courante:
if not block_computation:
mu = self.comp_etud_moy_ue(etudid, ue_id=ue["ue_id"], cnx=cnx)
@@ -981,7 +954,7 @@ class NotesTable:
return self.T
def get_etud_rang(self, etudid) -> str:
- return self.rangs.get(etudid, "999")
+ return self.etud_moy_gen_ranks.get(etudid, "999")
def get_etud_rang_group(self, etudid, group_id):
"""Returns rank of etud in this group and number of etuds in group.
@@ -1347,7 +1320,7 @@ class NotesTable:
# Rappel des épisodes précédents: T est une liste de liste
# Colonnes: 0 moy_gen, moy_ue1, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
- ues = self.get_ues() # incluant le(s) UE de sport
+ ues = self.get_ues_stat_dict() # incluant le(s) UE de sport
for t in self.T:
etudid = t[-1]
if etudid in results.etud_moy_gen: # evite les démissionnaires
@@ -1358,4 +1331,4 @@ class NotesTable:
# re-trie selon la nouvelle moyenne générale:
self.T.sort(key=self._row_key)
# Remplace aussi le rang:
- self.rangs = results.etud_moy_gen_ranks
+ self.etud_moy_gen_ranks = results.etud_moy_gen_ranks
diff --git a/app/scodoc/sco_abs_views.py b/app/scodoc/sco_abs_views.py
index 4e5aaa46..c719df31 100644
--- a/app/scodoc/sco_abs_views.py
+++ b/app/scodoc/sco_abs_views.py
@@ -118,7 +118,7 @@ def doSignaleAbsence(
mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
formsemestre_id = mod["formsemestre_id"]
nt = sco_cache.NotesTableCache.get(formsemestre_id)
- ues = nt.get_ues(etudid=etudid)
+ ues = nt.get_ues_stat_dict()
for ue in ues:
modimpls = nt.get_modimpls(ue_id=ue["ue_id"])
for modimpl in modimpls:
@@ -175,7 +175,7 @@ def SignaleAbsenceEtud(): # etudid implied
"abs_require_module", formsemestre_id
)
nt = sco_cache.NotesTableCache.get(formsemestre_id)
- ues = nt.get_ues(etudid=etudid)
+ ues = nt.get_ues_stat_dict()
if require_module:
menu_module = """