Génération bulletin BUT json

This commit is contained in:
Emmanuel Viennet 2021-12-05 20:21:51 +01:00
parent 1a673862aa
commit 3ba30f6250
12 changed files with 328 additions and 33 deletions

224
app/but/bulletin_but.py Normal file
View File

@ -0,0 +1,224 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
import datetime
import numpy as np
import pandas as pd
from app import db
from app.comp import df_cache, moy_ue, moy_mod, inscr_mod
from app.scodoc import sco_utils as scu
from app.scodoc.sco_cache import ResultatsSemestreBUTCache
from app.scodoc.sco_exceptions import ScoFormatError
from app.scodoc.sco_utils import jsnan
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
invalidate(): invalide données cachées
"""
_cached_attrs = (
"sem_cube",
"modimpl_inscr_df",
"modimpl_coefs_df",
"etud_moy_ue",
"modimpls_evals_poids",
"modimpls_evals_notes",
)
def __init__(self, formsemestre):
self.formsemestre = formsemestre
self.ues = formsemestre.query_ues().all()
self.modimpls = formsemestre.modimpls.all()
self.etuds = self.formsemestre.etuds.all()
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():
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"
(
self.sem_cube,
self.modimpls_evals_poids,
self.modimpls_evals_notes,
_,
) = moy_ue.notes_sem_load_cube(self.formsemestre)
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
self.formsemestre, ues=self.ues, modimpls=self.modimpls
)
# l'idx de la colonne du mod modimpl.id est
# modimpl_coefs_df.columns.get_loc(modimpl.id)
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
self.etud_moy_ue = moy_ue.compute_ue_moys(
self.sem_cube,
self.etuds,
self.modimpls,
self.ues,
self.modimpl_inscr_df,
self.modimpl_coefs_df,
)
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
"dict synthèse résultats dans l'UE pour les modules indiqués"
d = {}
etud_idx = self.etud_index[etud.id]
ue_idx = self.modimpl_coefs_df.index.get_loc(ue.id)
etud_moy_module = self.sem_cube[etud_idx] # module x UE
for mi in modimpls:
d[mi.module.code] = {
"id": mi.id,
"coef": self.modimpl_coefs_df[mi.id][ue.id],
"moyenne": jsnan(
etud_moy_module[self.modimpl_coefs_df.columns.get_loc(mi.id)][
ue_idx
]
),
}
return d
def etud_ue_results(self, etud, ue):
"dict synthèse résultats UE"
d = {
"id": ue.id,
"ECTS": {
"acquis": 0, # XXX TODO voir jury
"total": ue.ects,
},
"competence": None, # XXX TODO lien avec référentiel
"moyenne": jsnan(self.etud_moy_ue[ue.id].mean()),
"bonus": None, # XXX TODO
"malus": None, # XXX TODO voir ce qui est ici
"capitalise": None, # "AAAA-MM-JJ" TODO
"ressources": self.etud_ue_mod_results(etud, ue, self.ressources),
"saes": self.etud_ue_mod_results(etud, ue, self.saes),
}
return d
def etud_mods_results(self, etud, modimpls) -> dict:
"""dict synthèse résultats des modules indiqués,
avec évaluations de chacun."""
d = {}
etud_idx = self.etud_index[etud.id]
for mi in modimpls:
mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
# moyennes indicatives (moyennes de moyennes d'UE)
moyennes_etuds = np.nan_to_num(
self.sem_cube[:, mod_idx, :].mean(axis=1),
copy=False,
)
d[mi.module.code] = {
"id": mi.id,
"titre": mi.module.titre,
"code_apogee": mi.module.code_apogee,
"moyenne": {
"value": jsnan(self.sem_cube[etud_idx, mod_idx].mean()),
"min": jsnan(moyennes_etuds.min()),
"max": jsnan(moyennes_etuds.max()),
"moy": jsnan(moyennes_etuds.mean()),
},
"evaluations": [
self.etud_eval_results(etud, e)
for e in mi.evaluations
if e.visibulletin
],
}
return d
def etud_eval_results(self, etud, e) -> dict:
"dict resultats d'un étudiant à une évaluation"
eval_notes = self.modimpls_evals_notes[e.moduleimpl_id][str(e.id)] # pd.Series
notes_ok = eval_notes.where(eval_notes > -1000).dropna()
d = {
"id": e.id,
"description": e.description,
"date": e.jour.isoformat(),
"heure_debut": e.heure_debut.strftime("%H:%M") if e.heure_debut else None,
"heure_fin": e.heure_fin.strftime("%H:%M") if e.heure_debut else None,
"coef": e.coefficient,
"poids": {p.ue.acronyme: p.poids for p in e.ue_poids},
"note": {
"value": jsnan(
self.modimpls_evals_notes[e.moduleimpl_id][str(e.id)][etud.id]
),
"min": jsnan(notes_ok.min()),
"max": jsnan(notes_ok.max()),
"moy": jsnan(notes_ok.mean()),
},
}
return d
def bulletin_etud(self, etud, formsemestre) -> dict:
"""Le bulletin de l'étudiant dans ce semestre"""
d = {
"version": "0",
"type": "BUT",
"date": datetime.datetime.utcnow().isoformat() + "Z",
"etudiant": etud.to_dict_bul(),
"formation": {
"id": formsemestre.formation.id,
"acronyme": formsemestre.formation.acronyme,
"titre_officiel": formsemestre.formation.titre_officiel,
"titre": formsemestre.formation.titre,
},
"formsemestre_id": formsemestre.id,
"ressources": None, # XXX TODO
"saes": None, # XXX TODO
"ues": {ue.acronyme: self.etud_ue_results(etud, ue) for ue in self.ues},
"semestre": {
"notes": { # moyenne des moyennes générales du semestre
"value": jsnan("xxx"), # XXX TODO
"min": jsnan("0."),
"moy": jsnan("10.0"),
"max": jsnan("20.00"),
},
"rang": { # classement wrt moyenne général, indicatif
"value": None, # XXX TODO
"total": None,
},
"absences": { # XXX TODO
"injustifie": 1,
"total": 33,
},
"date_debut": formsemestre.date_debut.isoformat(),
"date_fin": formsemestre.date_fin.isoformat(),
"annee_universitaire": self.formsemestre.annee_scolaire_str(),
"inscription": "TODO-MM-JJ", # XXX TODO
"numero": formsemestre.semestre_id,
"decision": None, # XXX TODO
"situation": "Décision jury: Validé. Diplôme obtenu.", # XXX TODO
"date_jury": "AAAA-MM-JJ", # XXX TODO
"groupes": [], # XXX TODO
},
}
return d

View File

@ -14,16 +14,15 @@ from app import models
# sur test debug 116 etuds, 18 modules, on est autour de 250ms. # sur test debug 116 etuds, 18 modules, on est autour de 250ms.
# On a testé trois approches, ci-dessous (et retenu la 1ere) # On a testé trois approches, ci-dessous (et retenu la 1ere)
# #
def df_load_modimpl_inscr(formsemestre_id: int) -> pd.DataFrame: def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
"""Charge la matrice des inscriptions aux modules du semestre """Charge la matrice des inscriptions aux modules du semestre
rows: etudid rows: etudid
columns: moduleimpl_id (en chaîne) columns: moduleimpl_id (en chaîne)
value: bool (0/1 inscrit ou pas) value: bool (0/1 inscrit ou pas)
""" """
# méthode la moins lente: une requete par module, merge les dataframes # méthode la moins lente: une requete par module, merge les dataframes
sem = models.FormSemestre.query.get(formsemestre_id) moduleimpl_ids = [m.id for m in formsemestre.modimpls]
moduleimpl_ids = [m.id for m in sem.modimpls] etudids = [i.etudid for i in formsemestre.inscriptions]
etudids = [i.etudid for i in sem.inscriptions]
df = pd.DataFrame(index=etudids, dtype=int) df = pd.DataFrame(index=etudids, dtype=int)
for moduleimpl_id in moduleimpl_ids: for moduleimpl_id in moduleimpl_ids:
ins_df = pd.read_sql_query( ins_df = pd.read_sql_query(
@ -46,27 +45,25 @@ def df_load_modimpl_inscr(formsemestre_id: int) -> pd.DataFrame:
# timeit.timeit('x = df_load_module_inscr_v0(696)', number=100, globals=globals()) # timeit.timeit('x = df_load_module_inscr_v0(696)', number=100, globals=globals())
def df_load_modimpl_inscr_v0(formsemestre_id: int): def df_load_modimpl_inscr_v0(formsemestre):
# methode 0, pur SQL Alchemy, 1.5 à 2 fois plus lente # methode 0, pur SQL Alchemy, 1.5 à 2 fois plus lente
sem = models.FormSemestre.query.get(formsemestre_id) moduleimpl_ids = [m.id for m in formsemestre.modimpls]
moduleimpl_ids = [m.id for m in sem.modimpls] etudids = [i.etudid for i in formsemestre.inscriptions]
etudids = [i.etudid for i in sem.inscriptions]
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool) df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
for modimpl in sem.modimpls: for modimpl in formsemestre.modimpls:
ins_mod = df[modimpl.id] ins_mod = df[modimpl.id]
for inscr in modimpl.inscriptions: for inscr in modimpl.inscriptions:
ins_mod[inscr.etudid] = True ins_mod[inscr.etudid] = True
return df # x100 30.7s 46s 32s return df # x100 30.7s 46s 32s
def df_load_modimpl_inscr_v2(formsemestre_id): def df_load_modimpl_inscr_v2(formsemestre):
sem = models.FormSemestre.query.get(formsemestre_id) moduleimpl_ids = [m.id for m in formsemestre.modimpls]
moduleimpl_ids = [m.id for m in sem.modimpls] etudids = [i.etudid for i in formsemestre.inscriptions]
etudids = [i.etudid for i in sem.inscriptions]
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool) df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
cursor = db.engine.execute( cursor = db.engine.execute(
"select moduleimpl_id, etudid from notes_moduleimpl_inscription i, notes_moduleimpl m where i.moduleimpl_id = m.id and m.formsemestre_id = %(formsemestre_id)s", "select moduleimpl_id, etudid from notes_moduleimpl_inscription i, notes_moduleimpl m where i.moduleimpl_id = m.id and m.formsemestre_id = %(formsemestre_id)s",
{"formsemestre_id": formsemestre_id}, {"formsemestre_id": formsemestre.id},
) )
for moduleimpl_id, etudid in cursor: for moduleimpl_id, etudid in cursor:
df[moduleimpl_id][etudid] = True df[moduleimpl_id][etudid] = True

View File

@ -75,17 +75,21 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
return module_coefs_df, ues, modules return module_coefs_df, ues, modules
def df_load_modimpl_coefs(formsemestre: models.FormSemestre) -> pd.DataFrame: def df_load_modimpl_coefs(
formsemestre: models.FormSemestre, ues=None, modimpls=None
) -> pd.DataFrame:
"""Charge les coefs des modules du formsemestre indiqué. """Charge les coefs des modules du formsemestre indiqué.
Comme df_load_module_coefs mais prend seulement les UE Comme df_load_module_coefs mais prend seulement les UE
et modules du formsemestre. et modules du formsemestre.
Si ues et modimpls sont None, prend tous ceux du formsemestre.
Résultat: (module_coefs_df, ues, modules) Résultat: (module_coefs_df, ues, modules)
DataFrame rows = UEs, columns = modimpl, value = coef. DataFrame rows = UEs, columns = modimpl, value = coef.
""" """
if ues is None:
ues = formsemestre.query_ues().all() ues = formsemestre.query_ues().all()
ue_ids = [x.id for x in ues] ue_ids = [x.id for x in ues]
if modimpls is None:
modimpls = formsemestre.modimpls.all() modimpls = formsemestre.modimpls.all()
modimpl_ids = [x.id for x in modimpls] modimpl_ids = [x.id for x in modimpls]
mod2impl = {m.module.id: m.id for m in modimpls} mod2impl = {m.module.id: m.id for m in modimpls}
@ -115,13 +119,15 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
return modimpls_notes.swapaxes(0, 1) return modimpls_notes.swapaxes(0, 1)
def notes_sem_load_cube(formsemestre_id): def notes_sem_load_cube(formsemestre):
"""Calcule le cube des notes du semestre """Calcule le cube des notes du semestre
(charge toutes les notes, calcule les moyenne des modules (charge toutes les notes, calcule les moyenne des modules
et assemble le cube) et assemble le cube)
Resultat: ndarray (etuds x modimpls x UEs) Resultat: ndarray (etuds x modimpls x UEs)
""" """
formsemestre = FormSemestre.query.get(formsemestre_id) modimpls_evals_poids = {} # modimpl.id : evals_poids
modimpls_evals_notes = {} # modimpl.id : evals_notes
modimpls_evaluations = {} # modimpl.id : liste des évaluations
modimpls_notes = [] modimpls_notes = []
for modimpl in formsemestre.modimpls: for modimpl in formsemestre.modimpls:
evals_notes, evaluations = moy_mod.df_load_modimpl_notes(modimpl.id) evals_notes, evaluations = moy_mod.df_load_modimpl_notes(modimpl.id)
@ -129,8 +135,16 @@ def notes_sem_load_cube(formsemestre_id):
etuds_moy_module = moy_mod.compute_module_moy( etuds_moy_module = moy_mod.compute_module_moy(
evals_notes, evals_poids, evaluations evals_notes, evals_poids, evaluations
) )
modimpls_evals_poids[modimpl.id] = evals_poids
modimpls_evals_notes[modimpl.id] = evals_notes
modimpls_evaluations[modimpl.id] = evaluations
modimpls_notes.append(etuds_moy_module) modimpls_notes.append(etuds_moy_module)
return notes_sem_assemble_cube(modimpls_notes) return (
notes_sem_assemble_cube(modimpls_notes),
modimpls_evals_poids,
modimpls_evals_notes,
modimpls_evaluations,
)
def compute_ue_moys( def compute_ue_moys(

View File

@ -8,6 +8,7 @@ from app import db
from app.models import APO_CODE_STR_LEN from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN from app.models import CODE_STR_LEN
from app.scodoc import sco_photos
class Identite(db.Model): class Identite(db.Model):
@ -44,6 +45,7 @@ class Identite(db.Model):
# ne pas utiliser après migrate_scodoc7_dept_archives # ne pas utiliser après migrate_scodoc7_dept_archives
scodoc7_id = db.Column(db.Text(), nullable=True) scodoc7_id = db.Column(db.Text(), nullable=True)
# #
adresses = db.relationship("Adresse", lazy="dynamic", backref="etud")
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic") billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
def __repr__(self): def __repr__(self):
@ -64,6 +66,27 @@ class Identite(db.Model):
else: else:
return self.nom return self.nom
def to_dict_bul(self):
"""Infos exportées dans les bulletins"""
return {
"civilite": self.civilite,
"code_ine": self.code_nip,
"code_nip": self.code_ine,
"date_naissance": self.date_naissance.isoformat()
if self.date_naissance
else None,
"email": self.adresses[0].email or None
if self.adresses.count() > 0
else None,
"emailperso": self.adresses[0].emailperso or None
if self.adresses.count() > 0
else None,
"etudid": self.id,
"nom": self.nom_disp(),
"photo_url": sco_photos.get_etud_photo_url(self.id),
"prenom": self.prenom,
}
def inscription_courante(self): def inscription_courante(self):
"""La première inscription à un formsemestre _actuellement_ en cours. """La première inscription à un formsemestre _actuellement_ en cours.
None s'il n'y en a pas (ou plus, ou pas encore). None s'il n'y en a pas (ou plus, ou pas encore).

View File

@ -46,7 +46,7 @@ class Evaluation(db.Model):
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True) ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
def __repr__(self): def __repr__(self):
return f"<Evaluation {self.id} {self.jour.isoformat() if self.jour else ''}{self.description[:16] if self.description else ''}" return f"""<Evaluation {self.id} {self.jour.isoformat() if self.jour else ''} "{self.description[:16] if self.description else ''}">"""
def to_dict(self): def to_dict(self):
e = dict(self.__dict__) e = dict(self.__dict__)

View File

@ -58,6 +58,10 @@ class Formation(db.Model):
"""get l'instance de TypeParcours de cette formation""" """get l'instance de TypeParcours de cette formation"""
return sco_codes_parcours.get_parcours_from_code(self.type_parcours) return sco_codes_parcours.get_parcours_from_code(self.type_parcours)
def is_apc(self):
"True si formation APC avec SAE (BUT)"
return self.get_parcours().APC_SAE
def get_module_coefs(self, semestre_idx: int = None): def get_module_coefs(self, semestre_idx: int = None):
"""Les coefs des modules vers les UE (accès via cache)""" """Les coefs des modules vers les UE (accès via cache)"""
from app.comp import moy_ue from app.comp import moy_ue

View File

@ -178,6 +178,10 @@ class FormSemestre(db.Model):
else: else:
return ", ".join([u.get_nomcomplet() for u in self.responsables]) return ", ".join([u.get_nomcomplet() for u in self.responsables])
def annee_scolaire_str(self):
"2021 - 2022"
return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month)
def session_id(self) -> str: def session_id(self) -> str:
"""identifiant externe de semestre de formation """identifiant externe de semestre de formation
Exemple: RT-DUT-FI-S1-ANNEE Exemple: RT-DUT-FI-S1-ANNEE

View File

@ -155,6 +155,16 @@ class EvaluationCache(ScoDocCache):
cls.delete_many(evaluation_ids) cls.delete_many(evaluation_ids)
class ResultatsSemestreBUTCache(ScoDocCache):
"""Cache pour les résultats ResultatsSemestreBUT.
Clé: formsemestre_id
Valeur: { un paquet de dataframes }
"""
prefix = "RBUT"
timeout = 1 * 60 # ttl 1 minutes (en phase de mise au point)
class AbsSemEtudCache(ScoDocCache): class AbsSemEtudCache(ScoDocCache):
"""Cache pour les comptes d'absences d'un étudiant dans un semestre. """Cache pour les comptes d'absences d'un étudiant dans un semestre.
Ce cache étant indépendant des semestres, le compte peut être faux lorsqu'on Ce cache étant indépendant des semestres, le compte peut être faux lorsqu'on
@ -289,6 +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)
class DefferedSemCacheManager: class DefferedSemCacheManager:

View File

@ -43,7 +43,7 @@ Les images sont servies par ScoDoc, via la méthode getphotofile?etudid=xxx
""" """
from flask.helpers import make_response from flask.helpers import make_response, url_for
from app.scodoc.sco_exceptions import ScoGenError from app.scodoc.sco_exceptions import ScoGenError
import datetime import datetime
import glob import glob
@ -91,14 +91,17 @@ def photo_portal_url(etud):
return None return None
def get_etud_photo_url(etudid, size="small"):
return url_for(
"scolar.get_photo_image", scodoc_dept=g.scodoc_dept, etudid=etudid, size=size
)
def etud_photo_url(etud, size="small", fast=False): def etud_photo_url(etud, size="small", fast=False):
"""url to the image of the student, in "small" size or "orig" size. """url to the image of the student, in "small" size or "orig" size.
If ScoDoc doesn't have an image and a portal is configured, link to it. If ScoDoc doesn't have an image and a portal is configured, link to it.
""" """
photo_url = scu.ScoURL() + "/get_photo_image?etudid=%s&size=%s" % ( photo_url = get_etud_photo_url(etud["etudid"], size=size)
etud["etudid"],
size,
)
if fast: if fast:
return photo_url return photo_url
path = photo_pathname(etud, size=size) path = photo_pathname(etud, size=size)

View File

@ -202,6 +202,13 @@ def isnumber(x):
return isinstance(x, numbers.Number) return isinstance(x, numbers.Number)
def jsnan(x):
"if x is NaN, returns None"
if isinstance(x, numbers.Number) and np.isnan(x):
return None
return x
def join_words(*words): def join_words(*words):
words = [str(w).strip() for w in words if w is not None] words = [str(w).strip() for w in words if w is not None]
return " ".join([w for w in words if w]) return " ".join([w for w in words if w])

View File

@ -38,10 +38,11 @@ from operator import itemgetter
from xml.etree import ElementTree from xml.etree import ElementTree
import flask import flask
from flask import url_for from flask import url_for, jsonify
from flask import current_app, g, request from flask import current_app, g, request
from flask_login import current_user from flask_login import current_user
from werkzeug.utils import redirect from werkzeug.utils import redirect
from app.models.formsemestre import FormSemestre
from config import Config from config import Config
@ -49,7 +50,7 @@ from app import api
from app import db from app import db
from app import models from app import models
from app.auth.models import User from app.auth.models import User
from app.but import bulletin_but
from app.decorators import ( from app.decorators import (
scodoc, scodoc,
scodoc7func, scodoc7func,
@ -285,6 +286,13 @@ def formsemestre_bulletinetud(
raise ScoValueError("Paramètre manquant: spécifier code_nip ou etudid") raise ScoValueError("Paramètre manquant: spécifier code_nip ou etudid")
if not formsemestre_id: if not formsemestre_id:
raise ScoValueError("Paramètre manquant: formsemestre_id est requis") raise ScoValueError("Paramètre manquant: formsemestre_id est requis")
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if formsemestre.formation.is_apc():
etud = models.Identite.query.get_or_404(etudid)
r = bulletin_but.ResultatsSemestreBUT(formsemestre)
return jsonify(r.bulletin_etud(etud, formsemestre))
return sco_bulletins.formsemestre_bulletinetud( return sco_bulletins.formsemestre_bulletinetud(
etudid=etudid, etudid=etudid,
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,

View File

@ -53,7 +53,7 @@ def test_ue_moy(test_client):
# Les moduleimpls # Les moduleimpls
modimpls = [evaluation1.moduleimpl, evaluation2.moduleimpl] modimpls = [evaluation1.moduleimpl, evaluation2.moduleimpl]
# Check inscriptions modules # Check inscriptions modules
modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(formsemestre_id) modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(formsemestre)
assert (modimpl_inscr_df.values == np.array([[1, 1]])).all() assert (modimpl_inscr_df.values == np.array([[1, 1]])).all()
# Coefs des modules vers les UE: # Coefs des modules vers les UE:
modimpl_coefs_df, ues, modimpls = moy_ue.df_load_modimpl_coefs(formsemestre) modimpl_coefs_df, ues, modimpls = moy_ue.df_load_modimpl_coefs(formsemestre)
@ -69,7 +69,7 @@ def test_ue_moy(test_client):
_ = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, n1)]) _ = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, n1)])
_ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)]) _ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)])
# Recalcul des moyennes # Recalcul des moyennes
sem_cube = moy_ue.notes_sem_load_cube(formsemestre_id) sem_cube, _, _, _ = moy_ue.notes_sem_load_cube(formsemestre)
etuds = formsemestre.etuds.all() etuds = formsemestre.etuds.all()
etud_moy_ue = moy_ue.compute_ue_moys( etud_moy_ue = moy_ue.compute_ue_moys(
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df
@ -103,7 +103,7 @@ def test_ue_moy(test_client):
).first() ).first()
db.session.delete(inscr) db.session.delete(inscr)
db.session.commit() db.session.commit()
modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(formsemestre_id) modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(formsemestre)
assert (modimpl_inscr_df.values == np.array([[1, 0]])).all() assert (modimpl_inscr_df.values == np.array([[1, 0]])).all()
n1, n2 = 5.0, NOTES_NEUTRALISE n1, n2 = 5.0, NOTES_NEUTRALISE
# On ne doit pas pouvoir saisir de note sans être inscrit: # On ne doit pas pouvoir saisir de note sans être inscrit:
@ -114,7 +114,7 @@ def test_ue_moy(test_client):
exception_raised = True exception_raised = True
assert exception_raised assert exception_raised
# Recalcule les notes: # Recalcule les notes:
sem_cube = moy_ue.notes_sem_load_cube(formsemestre_id) sem_cube, _, _, _ = moy_ue.notes_sem_load_cube(formsemestre)
etuds = formsemestre.etuds.all() etuds = formsemestre.etuds.all()
etud_moy_ue = moy_ue.compute_ue_moys( etud_moy_ue = moy_ue.compute_ue_moys(
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df