Prépare modernisation de NT

This commit is contained in:
Emmanuel Viennet 2021-12-24 00:08:25 +01:00
parent 9eb2c2462b
commit dce7dc42cb
22 changed files with 346 additions and 293 deletions

View File

@ -4,7 +4,6 @@
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
from collections import defaultdict
import datetime import datetime
from flask import url_for, g from flask import url_for, g
import numpy as np import numpy as np
@ -15,62 +14,24 @@ from app import db
from app.comp import moy_ue, moy_sem, inscr_mod from app.comp import moy_ue, moy_sem, inscr_mod
from app.models import ModuleImpl from app.models import ModuleImpl
from app.scodoc import sco_utils as scu 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_bulletins_json
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.sco_utils import jsnan, fmt_note from app.scodoc.sco_utils import jsnan, fmt_note
from app.comp.res_sem import ResultatsSemestre, NotesTableCompat
class ResultatsSemestreBUT: class ResultatsSemestreBUT(NotesTableCompat):
"""Structure légère pour stocker les résultats du semestre et """Résultats BUT: organisation des calculs"""
générer les bulletins.
__init__ : charge depuis le cache ou calcule
"""
_cached_attrs = ( _cached_attrs = NotesTableCompat._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): def __init__(self, formsemestre):
self.formsemestre = formsemestre super().__init__(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
]
if not self.load_cached(): if not self.load_cached():
self.compute() self.compute()
self.store() 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): def compute(self):
"Charge les notes et inscriptions et calcule toutes les moyennes" "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) 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: def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
"dict synthèse résultats dans l'UE pour les modules indiqués" "dict synthèse résultats dans l'UE pour les modules indiqués"
d = {} d = {}
@ -233,7 +201,7 @@ class ResultatsSemestreBUT:
}, },
"formsemestre_id": formsemestre.id, "formsemestre_id": formsemestre.id,
"etat_inscription": etat_inscription, "etat_inscription": etat_inscription,
"options": bulletin_option_affichage(formsemestre), "options": sco_preferences.bulletin_option_affichage(formsemestre.id),
} }
semestre_infos = { semestre_infos = {
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo], "etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
@ -298,125 +266,3 @@ class ResultatsSemestreBUT:
) )
return d 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

View File

@ -314,13 +314,3 @@ def bulletin_but_xml_compat(
return None return None
else: else:
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING) 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)
"""

216
app/comp/res_sem.py Normal file
View File

@ -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

View File

@ -84,7 +84,11 @@ class FormSemestre(db.Model):
etapes = db.relationship( etapes = db.relationship(
"FormSemestreEtape", cascade="all,delete", backref="formsemestre" "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( etuds = db.relationship(
"Identite", "Identite",
secondary="notes_formsemestre_inscription", secondary="notes_formsemestre_inscription",

View File

@ -68,7 +68,7 @@ class TableTag(object):
self.taglist = [] self.taglist = []
self.resultats = {} self.resultats = {}
self.rangs = {} self.etud_moy_gen_ranks = {}
self.statistiques = {} self.statistiques = {}
# ***************************************************************************************************************** # *****************************************************************************************************************

View File

@ -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 from operator import itemgetter
@ -102,7 +104,7 @@ def comp_ranks(T):
def get_sem_ues_modimpls(formsemestre_id, modimpls=None): def get_sem_ues_modimpls(formsemestre_id, modimpls=None):
"""Get liste des UE du semestre (à partir des moduleimpls) """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: if modimpls is None:
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
@ -316,7 +318,7 @@ class NotesTable:
self.moy_min = self.moy_max = "NA" self.moy_min = self.moy_max = "NA"
# calcul rangs (/ moyenne generale) # calcul rangs (/ moyenne generale)
self.rangs = comp_ranks(T) self.etud_moy_gen_ranks = comp_ranks(T)
self.rangs_groupes = ( self.rangs_groupes = (
{} {}
@ -417,43 +419,14 @@ class NotesTable:
else: else:
return ' <font color="red">(%s)</font> ' % etat return ' <font color="red">(%s)</font> ' % etat
def get_ues(self, filter_sport=False, filter_non_inscrit=False, etudid=None): def get_ues_stat_dict(self, filter_sport=False): # was get_ues()
"""liste des ue, ordonnée par numero. """Liste des UEs, ordonnée par numero.
Si filter_non_inscrit, retire les UE dans lesquelles l'etudiant n'est
inscrit à aucun module.
Si filter_sport, retire les UE de type SPORT 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 return self._ues
if filter_sport:
ues_src = [ue for ue in self._ues if ue["type"] != UE_SPORT]
else: else:
ues_src = self._ues return [ue for ue in self._ues if ue["type"] != UE_SPORT]
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
def get_modimpls(self, ue_id=None): def get_modimpls(self, ue_id=None):
"liste des modules pour une UE (ou toutes si ue_id==None), triés par matières." "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. 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 sum_moy = 0 # la somme des moyennes générales valides
nb_moy = 0 # le nombre de moyennes générales valides nb_moy = 0 # le nombre de moyennes générales valides
for ue in ues: for ue in ues:
@ -561,9 +534,9 @@ class NotesTable:
i = 0 i = 0
for ue in ues: for ue in ues:
i += 1 i += 1
ue["nb_moy"] = len(ue["_notes"]) ue["nb_vals"] = len(ue["_notes"])
if ue["nb_moy"] > 0: if ue["nb_vals"] > 0:
ue["moy"] = sum(ue["_notes"]) / ue["nb_moy"] ue["moy"] = sum(ue["_notes"]) / ue["nb_vals"]
ue["max"] = max(ue["_notes"]) ue["max"] = max(ue["_notes"])
ue["min"] = min(ue["_notes"]) ue["min"] = min(ue["_notes"])
else: else:
@ -767,7 +740,7 @@ class NotesTable:
sem_ects_pot_fond = 0.0 sem_ects_pot_fond = 0.0
sem_ects_pot_pro = 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: # - On calcule la moyenne d'UE courante:
if not block_computation: if not block_computation:
mu = self.comp_etud_moy_ue(etudid, ue_id=ue["ue_id"], cnx=cnx) mu = self.comp_etud_moy_ue(etudid, ue_id=ue["ue_id"], cnx=cnx)
@ -981,7 +954,7 @@ class NotesTable:
return self.T return self.T
def get_etud_rang(self, etudid) -> str: 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): def get_etud_rang_group(self, etudid, group_id):
"""Returns rank of etud in this group and number of etuds in group. """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 # 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 # 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: for t in self.T:
etudid = t[-1] etudid = t[-1]
if etudid in results.etud_moy_gen: # evite les démissionnaires 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: # re-trie selon la nouvelle moyenne générale:
self.T.sort(key=self._row_key) self.T.sort(key=self._row_key)
# Remplace aussi le rang: # Remplace aussi le rang:
self.rangs = results.etud_moy_gen_ranks self.etud_moy_gen_ranks = results.etud_moy_gen_ranks

View File

@ -118,7 +118,7 @@ def doSignaleAbsence(
mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
formsemestre_id = mod["formsemestre_id"] formsemestre_id = mod["formsemestre_id"]
nt = sco_cache.NotesTableCache.get(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: for ue in ues:
modimpls = nt.get_modimpls(ue_id=ue["ue_id"]) modimpls = nt.get_modimpls(ue_id=ue["ue_id"])
for modimpl in modimpls: for modimpl in modimpls:
@ -175,7 +175,7 @@ def SignaleAbsenceEtud(): # etudid implied
"abs_require_module", formsemestre_id "abs_require_module", formsemestre_id
) )
nt = sco_cache.NotesTableCache.get(formsemestre_id) nt = sco_cache.NotesTableCache.get(formsemestre_id)
ues = nt.get_ues(etudid=etudid) ues = nt.get_ues_stat_dict()
if require_module: if require_module:
menu_module = """ menu_module = """
<script type="text/javascript"> <script type="text/javascript">

View File

@ -437,7 +437,7 @@ class ApoEtud(dict):
# Elements UE # Elements UE
decisions_ue = nt.get_etud_decision_ues(etudid) decisions_ue = nt.get_etud_decision_ues(etudid)
for ue in nt.get_ues(): for ue in nt.get_ues_stat_dict():
if code in ue["code_apogee"].split(","): if code in ue["code_apogee"].split(","):
if self.export_res_ues: if self.export_res_ues:
if decisions_ue and ue["ue_id"] in decisions_ue: if decisions_ue and ue["ue_id"] in decisions_ue:
@ -973,7 +973,7 @@ class ApoData(object):
continue continue
# associé à une UE: # associé à une UE:
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"]) nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
for ue in nt.get_ues(): for ue in nt.get_ues_stat_dict():
if code in ue["code_apogee"].split(","): if code in ue["code_apogee"].split(","):
s.add(code) s.add(code)
continue continue

View File

@ -218,10 +218,10 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
] # deprecated / keep it for backward compat in templates ] # deprecated / keep it for backward compat in templates
# --- Notes # --- Notes
ues = nt.get_ues() ues = nt.get_ues_stat_dict()
modimpls = nt.get_modimpls() modimpls = nt.get_modimpls()
moy_gen = nt.get_etud_moy_gen(etudid) moy_gen = nt.get_etud_moy_gen(etudid)
I["nb_inscrits"] = len(nt.rangs) I["nb_inscrits"] = len(nt.etud_moy_gen_ranks)
I["moy_gen"] = scu.fmt_note(moy_gen) I["moy_gen"] = scu.fmt_note(moy_gen)
I["moy_min"] = scu.fmt_note(nt.moy_min) I["moy_min"] = scu.fmt_note(nt.moy_min)
I["moy_max"] = scu.fmt_note(nt.moy_max) I["moy_max"] = scu.fmt_note(nt.moy_max)
@ -265,7 +265,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
I["rang_gr"] = rang_gr I["rang_gr"] = rang_gr
I["gr_name"] = gr_name I["gr_name"] = gr_name
I["ninscrits_gr"] = ninscrits_gr I["ninscrits_gr"] = ninscrits_gr
I["nbetuds"] = len(nt.rangs) I["nbetuds"] = len(nt.etud_moy_gen_ranks)
I["nb_demissions"] = nt.nb_demissions I["nb_demissions"] = nt.nb_demissions
I["nb_defaillants"] = nt.nb_defaillants I["nb_defaillants"] = nt.nb_defaillants
if prefs["bul_show_rangs"]: if prefs["bul_show_rangs"]:

View File

@ -153,9 +153,9 @@ def formsemestre_bulletinetud_published_dict(
pid = partition["partition_id"] pid = partition["partition_id"]
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid) partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
ues = nt.get_ues() ues = nt.get_ues_stat_dict()
modimpls = nt.get_modimpls() modimpls = nt.get_modimpls()
nbetuds = len(nt.rangs) nbetuds = len(nt.etud_moy_gen_ranks)
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid)) mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
if ( if (
nt.get_moduleimpls_attente() nt.get_moduleimpls_attente()

View File

@ -151,9 +151,9 @@ def make_xml_formsemestre_bulletinetud(
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid) partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
ues = nt.get_ues() ues = nt.get_ues_stat_dict()
modimpls = nt.get_modimpls() modimpls = nt.get_modimpls()
nbetuds = len(nt.rangs) nbetuds = len(nt.etud_moy_gen_ranks)
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid)) mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
if ( if (
nt.get_moduleimpls_attente() nt.get_moduleimpls_attente()

View File

@ -155,14 +155,14 @@ class EvaluationCache(ScoDocCache):
cls.delete_many(evaluation_ids) cls.delete_many(evaluation_ids)
class ResultatsSemestreBUTCache(ScoDocCache): class ResultatsSemestreCache(ScoDocCache):
"""Cache pour les résultats ResultatsSemestreBUT. """Cache pour les résultats ResultatsSemestre.
Clé: formsemestre_id Clé: formsemestre_id
Valeur: { un paquet de dataframes } Valeur: { un paquet de dataframes }
""" """
prefix = "RBUT" prefix = "RSEM"
timeout = 1 * 60 # ttl 1 minutes (en phase de mise au point) timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
class AbsSemEtudCache(ScoDocCache): class AbsSemEtudCache(ScoDocCache):
@ -299,7 +299,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
SemInscriptionsCache.delete_many(formsemestre_ids) SemInscriptionsCache.delete_many(formsemestre_ids)
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids) SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
ResultatsSemestreBUTCache.delete_many(formsemestre_ids) ResultatsSemestreCache.delete_many(formsemestre_ids)
class DefferedSemCacheManager: class DefferedSemCacheManager:

View File

@ -51,6 +51,7 @@ from app.scodoc import sco_formsemestre_edit
from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_formsemestre_status from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_parcours_dut from app.scodoc import sco_parcours_dut
from app.scodoc.sco_parcours_dut import etud_est_inscrit_ue
from app.scodoc import sco_photos from app.scodoc import sco_photos
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_pvjury from app.scodoc import sco_pvjury
@ -543,7 +544,7 @@ def formsemestre_recap_parcours_table(
nt = sco_cache.NotesTableCache.get( nt = sco_cache.NotesTableCache.get(
sem["formsemestre_id"] sem["formsemestre_id"]
) # > get_ues, get_etud_moy_gen, get_etud_ue_status ) # > get_ues_stat_dict, get_etud_moy_gen, get_etud_ue_status
if is_cur: if is_cur:
type_sem = "*" # now unused type_sem = "*" # now unused
class_sem = "sem_courant" class_sem = "sem_courant"
@ -582,8 +583,17 @@ def formsemestre_recap_parcours_table(
else: else:
H.append('<td colspan="%d"><em>en cours</em></td>') H.append('<td colspan="%d"><em>en cours</em></td>')
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
# acronymes UEs # acronymes UEs auxquelles l'étudiant est inscrit:
ues = nt.get_ues(filter_sport=True, filter_non_inscrit=True, etudid=etudid) # XXX il est probable que l'on doive ici ajouter les
# XXX UE capitalisées
ues = nt.get_ues_stat_dict(filter_sport=True)
cnx = ndb.GetDBConnexion()
ues = [
ue
for ue in ues
if etud_est_inscrit_ue(cnx, etudid, sem["formsemestre_id"], ue["ue_id"])
]
for ue in ues: for ue in ues:
H.append('<td class="ue_acro"><span>%s</span></td>' % ue["acronyme"]) H.append('<td class="ue_acro"><span>%s</span></td>' % ue["acronyme"])
if len(ues) < Se.nb_max_ue: if len(ues) < Se.nb_max_ue:

View File

@ -479,11 +479,13 @@ def get_etuds_with_capitalized_ue(formsemestre_id):
returns { ue_id : [ { infos } ] } returns { ue_id : [ { infos } ] }
""" """
UECaps = scu.DictDefault(defaultvalue=[]) UECaps = scu.DictDefault(defaultvalue=[])
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_ues, get_etud_ue_status nt = sco_cache.NotesTableCache.get(
formsemestre_id
) # > get_ues_stat_dict, get_etud_ue_status
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id} args={"formsemestre_id": formsemestre_id}
) )
ues = nt.get_ues() ues = nt.get_ues_stat_dict()
for ue in ues: for ue in ues:
for etud in inscrits: for etud in inscrits:
status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"]) status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])

View File

@ -109,7 +109,7 @@ def SituationEtudParcours(etud, formsemestre_id):
"""renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)""" """renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)"""
nt = sco_cache.NotesTableCache.get( nt = sco_cache.NotesTableCache.get(
formsemestre_id formsemestre_id
) # > get_etud_decision_sem, get_etud_moy_gen, get_ues, get_etud_ue_status, etud_check_conditions_ues ) # > get_etud_decision_sem, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, etud_check_conditions_ues
parcours = nt.parcours parcours = nt.parcours
# #
if parcours.ECTS_ONLY: if parcours.ECTS_ONLY:
@ -330,8 +330,10 @@ class SituationEtudParcoursGeneric(object):
ue_acros = {} # acronyme ue : 1 ue_acros = {} # acronyme ue : 1
nb_max_ue = 0 nb_max_ue = 0
for sem in sems: for sem in sems:
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"]) # > get_ues nt = sco_cache.NotesTableCache.get(
ues = nt.get_ues(filter_sport=True) sem["formsemestre_id"]
) # > get_ues_stat_dict
ues = nt.get_ues_stat_dict(filter_sport=True)
for ue in ues: for ue in ues:
ue_acros[ue["acronyme"]] = 1 ue_acros[ue["acronyme"]] = 1
nb_ue = len(ues) nb_ue = len(ues)
@ -419,9 +421,7 @@ class SituationEtudParcoursGeneric(object):
self.moy_gen >= (self.parcours.BARRE_MOY - scu.NOTES_TOLERANCE) self.moy_gen >= (self.parcours.BARRE_MOY - scu.NOTES_TOLERANCE)
) )
# conserve etat UEs # conserve etat UEs
ue_ids = [ ue_ids = [x["ue_id"] for x in self.nt.get_ues_stat_dict(filter_sport=True)]
x["ue_id"] for x in self.nt.get_ues(etudid=self.etudid, filter_sport=True)
]
self.ues_status = {} # ue_id : status self.ues_status = {} # ue_id : status
for ue_id in ue_ids: for ue_id in ue_ids:
self.ues_status[ue_id] = self.nt.get_etud_ue_status(self.etudid, ue_id) self.ues_status[ue_id] = self.nt.get_etud_ue_status(self.etudid, ue_id)
@ -903,8 +903,10 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
""" """
valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False) valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_ues, get_etud_ue_status nt = sco_cache.NotesTableCache.get(
ue_ids = [x["ue_id"] for x in nt.get_ues(etudid=etudid, filter_sport=True)] formsemestre_id
) # > get_ues_stat_dict, get_etud_ue_status
ue_ids = [x["ue_id"] for x in nt.get_ues_stat_dict(filter_sport=True)]
for ue_id in ue_ids: for ue_id in ue_ids:
ue_status = nt.get_etud_ue_status(etudid, ue_id) ue_status = nt.get_etud_ue_status(etudid, ue_id)
if not assiduite: if not assiduite:
@ -1000,7 +1002,7 @@ def formsemestre_has_decisions(formsemestre_id):
def etud_est_inscrit_ue(cnx, etudid, formsemestre_id, ue_id): def etud_est_inscrit_ue(cnx, etudid, formsemestre_id, ue_id):
"""Vrai si l'étudiant est inscrit a au moins un module de cette UE dans ce semestre""" """Vrai si l'étudiant est inscrit à au moins un module de cette UE dans ce semestre"""
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute( cursor.execute(
"""SELECT mi.* """SELECT mi.*

View File

@ -61,7 +61,7 @@ def etud_get_poursuite_info(sem, etud):
nt = sco_cache.NotesTableCache.get(s["formsemestre_id"]) nt = sco_cache.NotesTableCache.get(s["formsemestre_id"])
dec = nt.get_etud_decision_sem(etudid) dec = nt.get_etud_decision_sem(etudid)
# Moyennes et rangs des UE # Moyennes et rangs des UE
ues = nt.get_ues(filter_sport=True) ues = nt.get_ues_stat_dict(filter_sport=True)
moy_ues = [ moy_ues = [
( (
ue["acronyme"], ue["acronyme"],

View File

@ -2266,3 +2266,31 @@ def doc_preferences():
) )
return "\n".join([" | ".join(x) for x in L]) return "\n".join([" | ".join(x) for x in L])
def bulletin_option_affichage(formsemestre_id: int) -> dict:
"dict avec les options d'affichages (préférences) pour ce semestre"
prefs = 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}

View File

@ -52,7 +52,7 @@ def feuille_preparation_jury(formsemestre_id):
"Feuille excel pour preparation des jurys" "Feuille excel pour preparation des jurys"
nt = sco_cache.NotesTableCache.get( nt = sco_cache.NotesTableCache.get(
formsemestre_id formsemestre_id
) # > get_etudids, get_etud_moy_gen, get_ues, get_etud_ue_status, get_etud_decision_sem, identdict, ) # > get_etudids, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, get_etud_decision_sem, identdict,
etudids = nt.get_etudids(sorted=True) # tri par moy gen etudids = nt.get_etudids(sorted=True) # tri par moy gen
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@ -85,8 +85,8 @@ def feuille_preparation_jury(formsemestre_id):
if Se.prev: if Se.prev:
ntp = sco_cache.NotesTableCache.get( ntp = sco_cache.NotesTableCache.get(
Se.prev["formsemestre_id"] Se.prev["formsemestre_id"]
) # > get_ues, get_etud_ue_status, get_etud_moy_gen, get_etud_decision_sem ) # > get_ues_stat_dict, get_etud_ue_status, get_etud_moy_gen, get_etud_decision_sem
for ue in ntp.get_ues(filter_sport=True): for ue in ntp.get_ues_stat_dict(filter_sport=True):
ue_status = ntp.get_etud_ue_status(etudid, ue["ue_id"]) ue_status = ntp.get_etud_ue_status(etudid, ue["ue_id"])
ue_code_s = ( ue_code_s = (
ue["ue_code"] + "_%s" % ntp.sem["semestre_id"] ue["ue_code"] + "_%s" % ntp.sem["semestre_id"]
@ -102,7 +102,7 @@ def feuille_preparation_jury(formsemestre_id):
prev_code[etudid] += "+" # indique qu'il a servi a compenser prev_code[etudid] += "+" # indique qu'il a servi a compenser
moy[etudid] = nt.get_etud_moy_gen(etudid) moy[etudid] = nt.get_etud_moy_gen(etudid)
for ue in nt.get_ues(filter_sport=True): for ue in nt.get_ues_stat_dict(filter_sport=True):
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"] ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"]
moy_ue[ue_code_s][etudid] = ue_status["moy"] moy_ue[ue_code_s][etudid] = ue_status["moy"]
@ -310,9 +310,9 @@ def feuille_preparation_jury(formsemestre_id):
ws.append_blank_row() ws.append_blank_row()
ws.append_single_cell_row("Titre des UE") ws.append_single_cell_row("Titre des UE")
if prev_moy: if prev_moy:
for ue in ntp.get_ues(filter_sport=True): for ue in ntp.get_ues_stat_dict(filter_sport=True):
ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]])) ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
for ue in nt.get_ues(filter_sport=True): for ue in nt.get_ues_stat_dict(filter_sport=True):
ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]])) ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
# #
ws.append_blank_row() ws.append_blank_row()

View File

@ -161,7 +161,7 @@ def _comp_ects_by_ue_code_and_type(nt, decision_ues):
def _comp_ects_capitalises_by_ue_code(nt, etudid): def _comp_ects_capitalises_by_ue_code(nt, etudid):
"""Calcul somme des ECTS des UE capitalisees""" """Calcul somme des ECTS des UE capitalisees"""
ues = nt.get_ues() ues = nt.get_ues_stat_dict()
ects_by_ue_code = {} ects_by_ue_code = {}
for ue in ues: for ue in ues:
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])

View File

@ -304,9 +304,9 @@ def make_formsemestre_recapcomplet(
)[0] )[0]
nt = sco_cache.NotesTableCache.get( nt = sco_cache.NotesTableCache.get(
formsemestre_id formsemestre_id
) # > get_modimpls, get_ues, get_table_moyennes_triees, get_etud_decision_sem, get_etud_etat, get_etud_rang, get_nom_short, get_mod_stats, nt.moy_moy, get_etud_decision_sem, ) # > get_modimpls, get_ues_stat_dict, get_table_moyennes_triees, get_etud_decision_sem, get_etud_etat, get_etud_rang, get_nom_short, get_mod_stats, nt.moy_moy, get_etud_decision_sem,
modimpls = nt.get_modimpls() modimpls = nt.get_modimpls()
ues = nt.get_ues() # incluant le(s) UE de sport ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
# #
if formsemestre.formation.is_apc(): if formsemestre.formation.is_apc():
nt.apc_recompute_moyennes() nt.apc_recompute_moyennes()
@ -964,7 +964,7 @@ def _formsemestre_recapcomplet_json(
etudid = t[-1] etudid = t[-1]
if is_apc: if is_apc:
etud = Identite.query.get(etudid) etud = Identite.query.get(etudid)
r = bulletin_but.ResultatsSemestreBUT(formsemestre) r = bulletin_but.BulletinBUT(formsemestre)
bul = r.bulletin_etud(etud, formsemestre) bul = r.bulletin_etud(etud, formsemestre)
else: else:
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict( bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(

View File

@ -429,19 +429,10 @@ def SignaleAbsenceGrHebdo(
] ]
# #
modimpls_list = [] modimpls_list = []
# Initialize with first student ues = nt.get_ues_stat_dict()
ues = nt.get_ues(etudid=etuds[0]["etudid"])
for ue in ues: for ue in ues:
modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"]) modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"])
# Add modules other students are subscribed to
for etud in etuds[1:]:
modimpls_etud = []
ues = nt.get_ues(etudid=etud["etudid"])
for ue in ues:
modimpls_etud += nt.get_modimpls(ue_id=ue["ue_id"])
modimpls_list += [m for m in modimpls_etud if m not in modimpls_list]
menu_module = "" menu_module = ""
for modimpl in modimpls_list: for modimpl in modimpls_list:
if modimpl["moduleimpl_id"] == moduleimpl_id: if modimpl["moduleimpl_id"] == moduleimpl_id:
@ -606,19 +597,10 @@ def SignaleAbsenceGrSemestre(
# #
if etuds: if etuds:
modimpls_list = [] modimpls_list = []
# Initialize with first student ues = nt.get_ues_stat_dict()
ues = nt.get_ues(etudid=etuds[0]["etudid"])
for ue in ues: for ue in ues:
modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"]) modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"])
# Add modules other students are subscribed to
for etud in etuds[1:]:
modimpls_etud = []
ues = nt.get_ues(etudid=etud["etudid"])
for ue in ues:
modimpls_etud += nt.get_modimpls(ue_id=ue["ue_id"])
modimpls_list += [m for m in modimpls_etud if m not in modimpls_list]
menu_module = "" menu_module = ""
for modimpl in modimpls_list: for modimpl in modimpls_list:
if modimpl["moduleimpl_id"] == moduleimpl_id: if modimpl["moduleimpl_id"] == moduleimpl_id:
@ -750,8 +732,8 @@ def _gen_form_saisie_groupe(
if etud["cursem"]: if etud["cursem"]:
nt = sco_cache.NotesTableCache.get( nt = sco_cache.NotesTableCache.get(
etud["cursem"]["formsemestre_id"] etud["cursem"]["formsemestre_id"]
) # > get_ues, get_etud_ue_status ) # > get_ues_stat_dict, get_etud_ue_status
for ue in nt.get_ues(): for ue in nt.get_ues_stat_dict():
status = nt.get_etud_ue_status(etudid, ue["ue_id"]) status = nt.get_etud_ue_status(etudid, ue["ue_id"])
if status["is_capitalized"]: if status["is_capitalized"]:
cap.append(ue["acronyme"]) cap.append(ue["acronyme"])

View File

@ -296,7 +296,7 @@ def formsemestre_bulletinetud(
code_nip=str(code_nip) code_nip=str(code_nip)
).first_or_404() ).first_or_404()
if format == "json": if format == "json":
r = bulletin_but.ResultatsSemestreBUT(formsemestre) r = bulletin_but.BulletinBUT(formsemestre)
return jsonify(r.bulletin_etud(etud, formsemestre)) return jsonify(r.bulletin_etud(etud, formsemestre))
elif format == "html": elif format == "html":
return render_template( return render_template(