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

View File

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

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(
"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",

View File

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

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
@ -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 ' <font color="red">(%s)</font> ' % 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

View File

@ -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 = """
<script type="text/javascript">

View File

@ -437,7 +437,7 @@ class ApoEtud(dict):
# Elements UE
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 self.export_res_ues:
if decisions_ue and ue["ue_id"] in decisions_ue:
@ -973,7 +973,7 @@ class ApoData(object):
continue
# associé à une UE:
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(","):
s.add(code)
continue

View File

@ -218,10 +218,10 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
] # deprecated / keep it for backward compat in templates
# --- Notes
ues = nt.get_ues()
ues = nt.get_ues_stat_dict()
modimpls = nt.get_modimpls()
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_min"] = scu.fmt_note(nt.moy_min)
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["gr_name"] = gr_name
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_defaillants"] = nt.nb_defaillants
if prefs["bul_show_rangs"]:

View File

@ -153,9 +153,9 @@ def formsemestre_bulletinetud_published_dict(
pid = partition["partition_id"]
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()
nbetuds = len(nt.rangs)
nbetuds = len(nt.etud_moy_gen_ranks)
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
if (
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)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
ues = nt.get_ues()
ues = nt.get_ues_stat_dict()
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))
if (
nt.get_moduleimpls_attente()

View File

@ -155,14 +155,14 @@ class EvaluationCache(ScoDocCache):
cls.delete_many(evaluation_ids)
class ResultatsSemestreBUTCache(ScoDocCache):
"""Cache pour les résultats ResultatsSemestreBUT.
class ResultatsSemestreCache(ScoDocCache):
"""Cache pour les résultats ResultatsSemestre.
Clé: formsemestre_id
Valeur: { un paquet de dataframes }
"""
prefix = "RBUT"
timeout = 1 * 60 # ttl 1 minutes (en phase de mise au point)
prefix = "RSEM"
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
class AbsSemEtudCache(ScoDocCache):
@ -299,7 +299,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
SemInscriptionsCache.delete_many(formsemestre_ids)
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
ResultatsSemestreBUTCache.delete_many(formsemestre_ids)
ResultatsSemestreCache.delete_many(formsemestre_ids)
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_status
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_preferences
from app.scodoc import sco_pvjury
@ -543,7 +544,7 @@ def formsemestre_recap_parcours_table(
nt = sco_cache.NotesTableCache.get(
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:
type_sem = "*" # now unused
class_sem = "sem_courant"
@ -582,8 +583,17 @@ def formsemestre_recap_parcours_table(
else:
H.append('<td colspan="%d"><em>en cours</em></td>')
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
# acronymes UEs
ues = nt.get_ues(filter_sport=True, filter_non_inscrit=True, etudid=etudid)
# acronymes UEs auxquelles l'étudiant est inscrit:
# 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:
H.append('<td class="ue_acro"><span>%s</span></td>' % ue["acronyme"])
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 } ] }
"""
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(
args={"formsemestre_id": formsemestre_id}
)
ues = nt.get_ues()
ues = nt.get_ues_stat_dict()
for ue in ues:
for etud in inscrits:
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)"""
nt = sco_cache.NotesTableCache.get(
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
#
if parcours.ECTS_ONLY:
@ -330,8 +330,10 @@ class SituationEtudParcoursGeneric(object):
ue_acros = {} # acronyme ue : 1
nb_max_ue = 0
for sem in sems:
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"]) # > get_ues
ues = nt.get_ues(filter_sport=True)
nt = sco_cache.NotesTableCache.get(
sem["formsemestre_id"]
) # > get_ues_stat_dict
ues = nt.get_ues_stat_dict(filter_sport=True)
for ue in ues:
ue_acros[ue["acronyme"]] = 1
nb_ue = len(ues)
@ -419,9 +421,7 @@ class SituationEtudParcoursGeneric(object):
self.moy_gen >= (self.parcours.BARRE_MOY - scu.NOTES_TOLERANCE)
)
# conserve etat UEs
ue_ids = [
x["ue_id"] for x in self.nt.get_ues(etudid=self.etudid, filter_sport=True)
]
ue_ids = [x["ue_id"] for x in self.nt.get_ues_stat_dict(filter_sport=True)]
self.ues_status = {} # ue_id : status
for ue_id in ue_ids:
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)
cnx = ndb.GetDBConnexion()
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_ues, get_etud_ue_status
ue_ids = [x["ue_id"] for x in nt.get_ues(etudid=etudid, filter_sport=True)]
nt = sco_cache.NotesTableCache.get(
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:
ue_status = nt.get_etud_ue_status(etudid, ue_id)
if not assiduite:
@ -1000,7 +1002,7 @@ def formsemestre_has_decisions(formsemestre_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.execute(
"""SELECT mi.*

View File

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

View File

@ -2266,3 +2266,31 @@ def doc_preferences():
)
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"
nt = sco_cache.NotesTableCache.get(
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
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@ -85,8 +85,8 @@ def feuille_preparation_jury(formsemestre_id):
if Se.prev:
ntp = sco_cache.NotesTableCache.get(
Se.prev["formsemestre_id"]
) # > get_ues, get_etud_ue_status, get_etud_moy_gen, get_etud_decision_sem
for ue in ntp.get_ues(filter_sport=True):
) # > get_ues_stat_dict, get_etud_ue_status, get_etud_moy_gen, get_etud_decision_sem
for ue in ntp.get_ues_stat_dict(filter_sport=True):
ue_status = ntp.get_etud_ue_status(etudid, ue["ue_id"])
ue_code_s = (
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
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_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"]
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_single_cell_row("Titre des UE")
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"]]))
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_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):
"""Calcul somme des ECTS des UE capitalisees"""
ues = nt.get_ues()
ues = nt.get_ues_stat_dict()
ects_by_ue_code = {}
for ue in ues:
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])

View File

@ -304,9 +304,9 @@ def make_formsemestre_recapcomplet(
)[0]
nt = sco_cache.NotesTableCache.get(
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()
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():
nt.apc_recompute_moyennes()
@ -964,7 +964,7 @@ def _formsemestre_recapcomplet_json(
etudid = t[-1]
if is_apc:
etud = Identite.query.get(etudid)
r = bulletin_but.ResultatsSemestreBUT(formsemestre)
r = bulletin_but.BulletinBUT(formsemestre)
bul = r.bulletin_etud(etud, formsemestre)
else:
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(

View File

@ -429,19 +429,10 @@ def SignaleAbsenceGrHebdo(
]
#
modimpls_list = []
# Initialize with first student
ues = nt.get_ues(etudid=etuds[0]["etudid"])
ues = nt.get_ues_stat_dict()
for ue in ues:
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 = ""
for modimpl in modimpls_list:
if modimpl["moduleimpl_id"] == moduleimpl_id:
@ -606,19 +597,10 @@ def SignaleAbsenceGrSemestre(
#
if etuds:
modimpls_list = []
# Initialize with first student
ues = nt.get_ues(etudid=etuds[0]["etudid"])
ues = nt.get_ues_stat_dict()
for ue in ues:
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 = ""
for modimpl in modimpls_list:
if modimpl["moduleimpl_id"] == moduleimpl_id:
@ -750,8 +732,8 @@ def _gen_form_saisie_groupe(
if etud["cursem"]:
nt = sco_cache.NotesTableCache.get(
etud["cursem"]["formsemestre_id"]
) # > get_ues, get_etud_ue_status
for ue in nt.get_ues():
) # > get_ues_stat_dict, get_etud_ue_status
for ue in nt.get_ues_stat_dict():
status = nt.get_etud_ue_status(etudid, ue["ue_id"])
if status["is_capitalized"]:
cap.append(ue["acronyme"])

View File

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