forked from ScoDoc/DocScoDoc
Génération bulletin BUT json
This commit is contained in:
parent
1a673862aa
commit
3ba30f6250
224
app/but/bulletin_but.py
Normal file
224
app/but/bulletin_but.py
Normal 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
|
@ -14,16 +14,15 @@ from app import models
|
||||
# sur test debug 116 etuds, 18 modules, on est autour de 250ms.
|
||||
# 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
|
||||
rows: etudid
|
||||
columns: moduleimpl_id (en chaîne)
|
||||
value: bool (0/1 inscrit ou pas)
|
||||
"""
|
||||
# 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 sem.modimpls]
|
||||
etudids = [i.etudid for i in sem.inscriptions]
|
||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
||||
etudids = [i.etudid for i in formsemestre.inscriptions]
|
||||
df = pd.DataFrame(index=etudids, dtype=int)
|
||||
for moduleimpl_id in moduleimpl_ids:
|
||||
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())
|
||||
|
||||
|
||||
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
|
||||
sem = models.FormSemestre.query.get(formsemestre_id)
|
||||
moduleimpl_ids = [m.id for m in sem.modimpls]
|
||||
etudids = [i.etudid for i in sem.inscriptions]
|
||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
||||
etudids = [i.etudid for i in formsemestre.inscriptions]
|
||||
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]
|
||||
for inscr in modimpl.inscriptions:
|
||||
ins_mod[inscr.etudid] = True
|
||||
return df # x100 30.7s 46s 32s
|
||||
|
||||
|
||||
def df_load_modimpl_inscr_v2(formsemestre_id):
|
||||
sem = models.FormSemestre.query.get(formsemestre_id)
|
||||
moduleimpl_ids = [m.id for m in sem.modimpls]
|
||||
etudids = [i.etudid for i in sem.inscriptions]
|
||||
def df_load_modimpl_inscr_v2(formsemestre):
|
||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
||||
etudids = [i.etudid for i in formsemestre.inscriptions]
|
||||
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
|
||||
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",
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
{"formsemestre_id": formsemestre.id},
|
||||
)
|
||||
for moduleimpl_id, etudid in cursor:
|
||||
df[moduleimpl_id][etudid] = True
|
||||
|
@ -75,17 +75,21 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
||||
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é.
|
||||
|
||||
Comme df_load_module_coefs mais prend seulement les UE
|
||||
et modules du formsemestre.
|
||||
|
||||
Si ues et modimpls sont None, prend tous ceux du formsemestre.
|
||||
Résultat: (module_coefs_df, ues, modules)
|
||||
DataFrame rows = UEs, columns = modimpl, value = coef.
|
||||
"""
|
||||
if ues is None:
|
||||
ues = formsemestre.query_ues().all()
|
||||
ue_ids = [x.id for x in ues]
|
||||
if modimpls is None:
|
||||
modimpls = formsemestre.modimpls.all()
|
||||
modimpl_ids = [x.id for x 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)
|
||||
|
||||
|
||||
def notes_sem_load_cube(formsemestre_id):
|
||||
def notes_sem_load_cube(formsemestre):
|
||||
"""Calcule le cube des notes du semestre
|
||||
(charge toutes les notes, calcule les moyenne des modules
|
||||
et assemble le cube)
|
||||
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 = []
|
||||
for modimpl in formsemestre.modimpls:
|
||||
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(
|
||||
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)
|
||||
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(
|
||||
|
@ -8,6 +8,7 @@ from app import db
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
from app.scodoc import sco_photos
|
||||
|
||||
|
||||
class Identite(db.Model):
|
||||
@ -44,6 +45,7 @@ class Identite(db.Model):
|
||||
# ne pas utiliser après migrate_scodoc7_dept_archives
|
||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||
#
|
||||
adresses = db.relationship("Adresse", lazy="dynamic", backref="etud")
|
||||
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
||||
|
||||
def __repr__(self):
|
||||
@ -64,6 +66,27 @@ class Identite(db.Model):
|
||||
else:
|
||||
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):
|
||||
"""La première inscription à un formsemestre _actuellement_ en cours.
|
||||
None s'il n'y en a pas (ou plus, ou pas encore).
|
||||
|
@ -46,7 +46,7 @@ class Evaluation(db.Model):
|
||||
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
|
||||
|
||||
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):
|
||||
e = dict(self.__dict__)
|
||||
|
@ -58,6 +58,10 @@ class Formation(db.Model):
|
||||
"""get l'instance de TypeParcours de cette formation"""
|
||||
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):
|
||||
"""Les coefs des modules vers les UE (accès via cache)"""
|
||||
from app.comp import moy_ue
|
||||
|
@ -178,6 +178,10 @@ class FormSemestre(db.Model):
|
||||
else:
|
||||
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:
|
||||
"""identifiant externe de semestre de formation
|
||||
Exemple: RT-DUT-FI-S1-ANNEE
|
||||
|
@ -155,6 +155,16 @@ class EvaluationCache(ScoDocCache):
|
||||
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):
|
||||
"""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
|
||||
@ -289,6 +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)
|
||||
|
||||
|
||||
class DefferedSemCacheManager:
|
||||
|
@ -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
|
||||
import datetime
|
||||
import glob
|
||||
@ -91,14 +91,17 @@ def photo_portal_url(etud):
|
||||
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):
|
||||
"""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.
|
||||
"""
|
||||
photo_url = scu.ScoURL() + "/get_photo_image?etudid=%s&size=%s" % (
|
||||
etud["etudid"],
|
||||
size,
|
||||
)
|
||||
photo_url = get_etud_photo_url(etud["etudid"], size=size)
|
||||
if fast:
|
||||
return photo_url
|
||||
path = photo_pathname(etud, size=size)
|
||||
|
@ -202,6 +202,13 @@ def isnumber(x):
|
||||
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):
|
||||
words = [str(w).strip() for w in words if w is not None]
|
||||
return " ".join([w for w in words if w])
|
||||
|
@ -38,10 +38,11 @@ from operator import itemgetter
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import flask
|
||||
from flask import url_for
|
||||
from flask import url_for, jsonify
|
||||
from flask import current_app, g, request
|
||||
from flask_login import current_user
|
||||
from werkzeug.utils import redirect
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
from config import Config
|
||||
|
||||
@ -49,7 +50,7 @@ from app import api
|
||||
from app import db
|
||||
from app import models
|
||||
from app.auth.models import User
|
||||
|
||||
from app.but import bulletin_but
|
||||
from app.decorators import (
|
||||
scodoc,
|
||||
scodoc7func,
|
||||
@ -285,6 +286,13 @@ def formsemestre_bulletinetud(
|
||||
raise ScoValueError("Paramètre manquant: spécifier code_nip ou etudid")
|
||||
if not formsemestre_id:
|
||||
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(
|
||||
etudid=etudid,
|
||||
formsemestre_id=formsemestre_id,
|
||||
|
@ -53,7 +53,7 @@ def test_ue_moy(test_client):
|
||||
# Les moduleimpls
|
||||
modimpls = [evaluation1.moduleimpl, evaluation2.moduleimpl]
|
||||
# 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()
|
||||
# Coefs des modules vers les UE:
|
||||
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, evaluation2.id, [(etudid, n2)])
|
||||
# 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()
|
||||
etud_moy_ue = moy_ue.compute_ue_moys(
|
||||
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df
|
||||
@ -103,7 +103,7 @@ def test_ue_moy(test_client):
|
||||
).first()
|
||||
db.session.delete(inscr)
|
||||
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()
|
||||
n1, n2 = 5.0, NOTES_NEUTRALISE
|
||||
# On ne doit pas pouvoir saisir de note sans être inscrit:
|
||||
@ -114,7 +114,7 @@ def test_ue_moy(test_client):
|
||||
exception_raised = True
|
||||
assert exception_raised
|
||||
# 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()
|
||||
etud_moy_ue = moy_ue.compute_ue_moys(
|
||||
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df
|
||||
|
Loading…
Reference in New Issue
Block a user