forked from ScoDoc/ScoDoc
WIP: refactoring calculs
This commit is contained in:
parent
adaef9fd24
commit
0fe5cdb409
@ -4,19 +4,17 @@
|
|||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
"""Génération bulletin BUT
|
||||||
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
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.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
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 fmt_note
|
||||||
from app.comp.res_sem import ResultatsSemestre, NotesTableCompat
|
from app.comp.res_sem import ResultatsSemestre, NotesTableCompat
|
||||||
|
|
||||||
|
|
||||||
@ -37,9 +35,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
(
|
(
|
||||||
self.sem_cube,
|
self.sem_cube,
|
||||||
self.modimpls_evals_poids,
|
self.modimpls_evals_poids,
|
||||||
self.modimpls_evals_notes,
|
self.modimpls_results,
|
||||||
modimpls_evaluations,
|
|
||||||
self.modimpls_evaluations_complete,
|
|
||||||
) = moy_ue.notes_sem_load_cube(self.formsemestre)
|
) = moy_ue.notes_sem_load_cube(self.formsemestre)
|
||||||
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(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.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
|
||||||
@ -74,16 +70,16 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
etud_idx = self.etud_index[etud.id]
|
etud_idx = self.etud_index[etud.id]
|
||||||
ue_idx = self.modimpl_coefs_df.index.get_loc(ue.id)
|
ue_idx = self.modimpl_coefs_df.index.get_loc(ue.id)
|
||||||
etud_moy_module = self.sem_cube[etud_idx] # module x UE
|
etud_moy_module = self.sem_cube[etud_idx] # module x UE
|
||||||
for mi in modimpls:
|
for modimpl in modimpls:
|
||||||
coef = self.modimpl_coefs_df[mi.id][ue.id]
|
coef = self.modimpl_coefs_df[modimpl.id][ue.id]
|
||||||
if coef > 0:
|
if coef > 0:
|
||||||
d[mi.module.code] = {
|
d[modimpl.module.code] = {
|
||||||
"id": mi.id,
|
"id": modimpl.id,
|
||||||
"coef": coef,
|
"coef": coef,
|
||||||
"moyenne": fmt_note(
|
"moyenne": fmt_note(
|
||||||
etud_moy_module[self.modimpl_coefs_df.columns.get_loc(mi.id)][
|
etud_moy_module[
|
||||||
ue_idx
|
self.modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||||
]
|
][ue_idx]
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
@ -117,7 +113,7 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
avec évaluations de chacun."""
|
avec évaluations de chacun."""
|
||||||
d = {}
|
d = {}
|
||||||
# etud_idx = self.etud_index[etud.id]
|
# etud_idx = self.etud_index[etud.id]
|
||||||
for mi in modimpls:
|
for modimpl in modimpls:
|
||||||
# mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
|
# mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
|
||||||
# # moyennes indicatives (moyennes de moyennes d'UE)
|
# # moyennes indicatives (moyennes de moyennes d'UE)
|
||||||
# try:
|
# try:
|
||||||
@ -131,14 +127,15 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
# moy_indicative_mod = np.nanmean(self.sem_cube[etud_idx, mod_idx])
|
# moy_indicative_mod = np.nanmean(self.sem_cube[etud_idx, mod_idx])
|
||||||
# except RuntimeWarning: # all nans in np.nanmean
|
# except RuntimeWarning: # all nans in np.nanmean
|
||||||
# pass
|
# pass
|
||||||
d[mi.module.code] = {
|
modimpl_results = self.modimpls_results[modimpl.id]
|
||||||
"id": mi.id,
|
d[modimpl.module.code] = {
|
||||||
"titre": mi.module.titre,
|
"id": modimpl.id,
|
||||||
"code_apogee": mi.module.code_apogee,
|
"titre": modimpl.module.titre,
|
||||||
|
"code_apogee": modimpl.module.code_apogee,
|
||||||
"url": url_for(
|
"url": url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
moduleimpl_id=mi.id,
|
moduleimpl_id=modimpl.id,
|
||||||
),
|
),
|
||||||
"moyenne": {
|
"moyenne": {
|
||||||
# # moyenne indicative de module: moyenne des UE, ignorant celles sans notes (nan)
|
# # moyenne indicative de module: moyenne des UE, ignorant celles sans notes (nan)
|
||||||
@ -149,16 +146,17 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
},
|
},
|
||||||
"evaluations": [
|
"evaluations": [
|
||||||
self.etud_eval_results(etud, e)
|
self.etud_eval_results(etud, e)
|
||||||
for eidx, e in enumerate(mi.evaluations)
|
for e in modimpl.evaluations
|
||||||
if e.visibulletin
|
if e.visibulletin
|
||||||
and self.modimpls_evaluations_complete[mi.id][eidx]
|
and modimpl_results.evaluations_etat[e.id].is_complete
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def etud_eval_results(self, etud, e) -> dict:
|
def etud_eval_results(self, etud, e) -> dict:
|
||||||
"dict resultats d'un étudiant à une évaluation"
|
"dict resultats d'un étudiant à une évaluation"
|
||||||
eval_notes = self.modimpls_evals_notes[e.moduleimpl_id][e.id] # pd.Series
|
# eval_notes est une pd.Series avec toutes les notes des étudiants inscrits
|
||||||
|
eval_notes = self.modimpls_results[e.moduleimpl_id].evals_notes[e.id]
|
||||||
notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
|
notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
|
||||||
d = {
|
d = {
|
||||||
"id": e.id,
|
"id": e.id,
|
||||||
@ -170,7 +168,7 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
"poids": {p.ue.acronyme: p.poids for p in e.ue_poids},
|
"poids": {p.ue.acronyme: p.poids for p in e.ue_poids},
|
||||||
"note": {
|
"note": {
|
||||||
"value": fmt_note(
|
"value": fmt_note(
|
||||||
self.modimpls_evals_notes[e.moduleimpl_id][e.id][etud.id],
|
eval_notes[etud.id],
|
||||||
note_max=e.note_max,
|
note_max=e.note_max,
|
||||||
),
|
),
|
||||||
"min": fmt_note(notes_ok.min()),
|
"min": fmt_note(notes_ok.min()),
|
||||||
@ -212,8 +210,8 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
"numero": formsemestre.semestre_id,
|
"numero": formsemestre.semestre_id,
|
||||||
"groupes": [], # XXX TODO
|
"groupes": [], # XXX TODO
|
||||||
"absences": { # XXX TODO
|
"absences": { # XXX TODO
|
||||||
"injustifie": 1,
|
"injustifie": -1,
|
||||||
"total": 33,
|
"total": -1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
semestre_infos.update(
|
semestre_infos.update(
|
||||||
|
@ -108,8 +108,8 @@ def bulletin_but_xml_compat(
|
|||||||
code_ine=etud.code_ine or "",
|
code_ine=etud.code_ine or "",
|
||||||
nom=scu.quote_xml_attr(etud.nom),
|
nom=scu.quote_xml_attr(etud.nom),
|
||||||
prenom=scu.quote_xml_attr(etud.prenom),
|
prenom=scu.quote_xml_attr(etud.prenom),
|
||||||
civilite=scu.quote_xml_attr(etud.civilite_str()),
|
civilite=scu.quote_xml_attr(etud.civilite_str),
|
||||||
sexe=scu.quote_xml_attr(etud.civilite_str()), # compat
|
sexe=scu.quote_xml_attr(etud.civilite_str), # compat
|
||||||
photo_url=scu.quote_xml_attr(sco_photos.get_etud_photo_url(etud.id)),
|
photo_url=scu.quote_xml_attr(sco_photos.get_etud_photo_url(etud.id)),
|
||||||
email=scu.quote_xml_attr(etud.get_first_email() or ""),
|
email=scu.quote_xml_attr(etud.get_first_email() or ""),
|
||||||
emailperso=scu.quote_xml_attr(etud.get_first_email("emailperso") or ""),
|
emailperso=scu.quote_xml_attr(etud.get_first_email("emailperso") or ""),
|
||||||
@ -216,9 +216,9 @@ def bulletin_but_xml_compat(
|
|||||||
Element(
|
Element(
|
||||||
"note",
|
"note",
|
||||||
value=scu.fmt_note(
|
value=scu.fmt_note(
|
||||||
results.modimpls_evals_notes[e.moduleimpl_id][
|
results.modimpls_results[
|
||||||
e.id
|
e.moduleimpl_id
|
||||||
][etud.id]
|
].evals_notes[e.id][etud.id]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
37
app/comp/aux.py
Normal file
37
app/comp/aux.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
"""Quelques classes auxiliaires pour les calculs des notes
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
"Tous les attributs dans un dict"
|
||||||
|
return {
|
||||||
|
"min": self.min,
|
||||||
|
"max": self.max,
|
||||||
|
"moy": self.moy,
|
||||||
|
"size": self.size,
|
||||||
|
"nb_vals": self.nb_vals,
|
||||||
|
}
|
@ -43,7 +43,7 @@ class ModuleCoefsCache(sco_cache.ScoDocCache):
|
|||||||
class EvaluationsPoidsCache(sco_cache.ScoDocCache):
|
class EvaluationsPoidsCache(sco_cache.ScoDocCache):
|
||||||
"""Cache for poids evals
|
"""Cache for poids evals
|
||||||
Clé: moduleimpl_id
|
Clé: moduleimpl_id
|
||||||
Valeur: DataFrame (df_load_evaluations_poids)
|
Valeur: DataFrame (load_evaluations_poids)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "EPC"
|
prefix = "EPC"
|
||||||
|
@ -31,17 +31,217 @@ Rappel: pour éviter les confusions, on appelera *poids* les coefficients d'une
|
|||||||
évaluation dans un module, et *coefficients* ceux utilisés pour le calcul de la
|
évaluation dans un module, et *coefficients* ceux utilisés pour le calcul de la
|
||||||
moyenne générale d'une UE.
|
moyenne générale d'une UE.
|
||||||
"""
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from pandas.core.frame import DataFrame
|
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import models
|
|
||||||
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
|
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
def df_load_evaluations_poids(
|
@dataclass
|
||||||
|
class EvaluationEtat:
|
||||||
|
"""Classe pour stocker quelques infos sur les résultats d'une évaluation"""
|
||||||
|
|
||||||
|
evaluation_id: int
|
||||||
|
nb_attente: int
|
||||||
|
is_complete: bool
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleImplResultsAPC:
|
||||||
|
"""Les notes des étudiants d'un moduleimpl.
|
||||||
|
Les poids des évals sont à part car on a a besoin sans les notes pour les tableaux
|
||||||
|
de bord.
|
||||||
|
Les attributs sont tous des objets simples cachables dans Redis;
|
||||||
|
les caches sont gérés par ResultatsSemestre.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, moduleimpl: ModuleImpl):
|
||||||
|
self.moduleimpl_id = moduleimpl.id
|
||||||
|
self.module_id = moduleimpl.module.id
|
||||||
|
self.etudids = None
|
||||||
|
"liste des étudiants inscrits au SEMESTRE"
|
||||||
|
self.nb_inscrits_module = None
|
||||||
|
"nombre d'inscrits (non DEM) au module"
|
||||||
|
self.evaluations_completes = []
|
||||||
|
"séquence de booléens, indiquant les évals à prendre en compte."
|
||||||
|
self.evaluations_etat = {}
|
||||||
|
"{ evaluation_id: EvaluationEtat }"
|
||||||
|
#
|
||||||
|
self.evals_notes = None
|
||||||
|
"""DataFrame, colonnes: EVALS, Lignes: etudid
|
||||||
|
valeur: notes brutes, float ou NOTES_ATTENTE, NOTES_NEUTRALISE,
|
||||||
|
NOTES_ABSENCE.
|
||||||
|
Les NaN désignent les notes manquantes (non saisies).
|
||||||
|
"""
|
||||||
|
self.etuds_moy_module = None
|
||||||
|
"""DataFrame, colonnes UE, lignes etud
|
||||||
|
= la note de l'étudiant dans chaque UE pour ce module.
|
||||||
|
ou NaN si les évaluations (dans lesquelles l'étudiant a des notes)
|
||||||
|
ne donnent pas de coef vers cette UE.
|
||||||
|
"""
|
||||||
|
self.load_notes()
|
||||||
|
|
||||||
|
def load_notes(self): # ré-écriture de df_load_modimpl_notes
|
||||||
|
"""Charge toutes les notes de toutes les évaluations du module.
|
||||||
|
Dataframe evals_notes
|
||||||
|
colonnes: le nom de la colonne est l'evaluation_id (int)
|
||||||
|
index (lignes): etudid (int)
|
||||||
|
|
||||||
|
L'ensemble des étudiants est celui des inscrits au SEMESTRE.
|
||||||
|
|
||||||
|
Les notes sont "brutes" (séries de floats) et peuvent prendre les valeurs:
|
||||||
|
note : float (valeur enregistrée brute, NON normalisée sur 20)
|
||||||
|
pas de note: NaN (rien en bd, ou étudiant non inscrit au module)
|
||||||
|
absent: NOTES_ABSENCE (NULL en bd)
|
||||||
|
excusé: NOTES_NEUTRALISE (voir sco_utils)
|
||||||
|
attente: NOTES_ATTENTE
|
||||||
|
|
||||||
|
Évaluation "complete" (prise en compte dans les calculs) si:
|
||||||
|
- soit tous les étudiants inscrits au module ont des notes
|
||||||
|
- soit elle a été déclarée "à prise ne compte immédiate" (publish_incomplete)
|
||||||
|
|
||||||
|
Évaluation "attente" (prise en compte dans les calculs, mais il y
|
||||||
|
manque des notes) ssi il y a des étudiants inscrits au semestre et au module
|
||||||
|
qui ont des notes ATT.
|
||||||
|
"""
|
||||||
|
moduleimpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||||
|
self.etudids = self._etudids()
|
||||||
|
|
||||||
|
# --- Calcul nombre d'inscrits pour déterminer les évaluations "completes":
|
||||||
|
# on prend les inscrits au module ET au semestre (donc sans démissionnaires)
|
||||||
|
inscrits_module = {ins.etud.id for ins in moduleimpl.inscriptions}.intersection(
|
||||||
|
self.etudids
|
||||||
|
)
|
||||||
|
self.nb_inscrits_module = len(inscrits_module)
|
||||||
|
|
||||||
|
# dataFrame vide, index = tous les inscrits au SEMESTRE
|
||||||
|
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
||||||
|
self.evaluations_completes = []
|
||||||
|
for evaluation in moduleimpl.evaluations:
|
||||||
|
eval_df = self._load_evaluation_notes(evaluation)
|
||||||
|
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
||||||
|
# ou évaluaton déclarée "à prise en compte immédiate"
|
||||||
|
is_complete = (
|
||||||
|
len(set(eval_df.index).intersection(self.etudids))
|
||||||
|
== self.nb_inscrits_module
|
||||||
|
) or evaluation.publish_incomplete # immédiate
|
||||||
|
self.evaluations_completes.append(is_complete)
|
||||||
|
|
||||||
|
# NULL en base => ABS (= -999)
|
||||||
|
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
||||||
|
# Ce merge ne garde que les étudiants inscrits au module
|
||||||
|
# et met à NULL les notes non présentes
|
||||||
|
# (notes non saisies ou etuds non inscrits au module):
|
||||||
|
evals_notes = evals_notes.merge(
|
||||||
|
eval_df, how="left", left_index=True, right_index=True
|
||||||
|
)
|
||||||
|
# Notes en attente: (on prend dans evals_notes pour ne pas avoir les dem.)
|
||||||
|
nb_att = sum(evals_notes[str(evaluation.id)] == scu.NOTES_ATTENTE)
|
||||||
|
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
||||||
|
evaluation_id=evaluation.id, nb_attente=nb_att, is_complete=is_complete
|
||||||
|
)
|
||||||
|
|
||||||
|
# Force columns names to integers (evaluation ids)
|
||||||
|
evals_notes.columns = pd.Int64Index(
|
||||||
|
[int(x) for x in evals_notes.columns], dtype="int"
|
||||||
|
)
|
||||||
|
self.evals_notes = evals_notes
|
||||||
|
|
||||||
|
def _load_evaluation_notes(self, evaluation: Evaluation) -> pd.DataFrame:
|
||||||
|
"""Charge les notes de l'évaluation
|
||||||
|
Resultat: dataframe, index: etudid ayant une note, valeur: note brute.
|
||||||
|
"""
|
||||||
|
eval_df = pd.read_sql_query(
|
||||||
|
"""SELECT n.etudid, n.value AS "%(evaluation_id)s"
|
||||||
|
FROM notes_notes n, notes_moduleimpl_inscription i
|
||||||
|
WHERE evaluation_id=%(evaluation_id)s
|
||||||
|
AND n.etudid = i.etudid
|
||||||
|
AND i.moduleimpl_id = %(moduleimpl_id)s
|
||||||
|
""",
|
||||||
|
db.engine,
|
||||||
|
params={
|
||||||
|
"evaluation_id": evaluation.id,
|
||||||
|
"moduleimpl_id": evaluation.moduleimpl.id,
|
||||||
|
},
|
||||||
|
index_col="etudid",
|
||||||
|
)
|
||||||
|
eval_df[str(evaluation.id)] = pd.to_numeric(eval_df[str(evaluation.id)])
|
||||||
|
return eval_df
|
||||||
|
|
||||||
|
def _etudids(self):
|
||||||
|
"""L'index du dataframe est la liste des étudiants inscrits au semestre,
|
||||||
|
sans les démissionnaires.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
e.etudid
|
||||||
|
for e in ModuleImpl.query.get(self.moduleimpl_id).formsemestre.get_inscrits(
|
||||||
|
include_dem=False
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def compute_module_moy(
|
||||||
|
self,
|
||||||
|
evals_poids_df: pd.DataFrame,
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""Calcule les moyennes des étudiants dans ce module
|
||||||
|
|
||||||
|
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||||
|
|
||||||
|
Résultat: DataFrame, colonnes UE, lignes etud
|
||||||
|
= la note de l'étudiant dans chaque UE pour ce module.
|
||||||
|
ou NaN si les évaluations (dans lesquelles l'étudiant a des notes)
|
||||||
|
ne donnent pas de coef vers cette UE.
|
||||||
|
"""
|
||||||
|
moduleimpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||||
|
nb_etuds, nb_evals = self.evals_notes.shape
|
||||||
|
nb_ues = evals_poids_df.shape[1]
|
||||||
|
assert evals_poids_df.shape[0] == nb_evals # compat notes/poids
|
||||||
|
if nb_etuds == 0:
|
||||||
|
return pd.DataFrame(index=[], columns=evals_poids_df.columns)
|
||||||
|
# Coefficients des évaluations, met à zéro ceux des évals incomplètes:
|
||||||
|
evals_coefs = (
|
||||||
|
np.array(
|
||||||
|
[e.coefficient for e in moduleimpl.evaluations],
|
||||||
|
dtype=float,
|
||||||
|
)
|
||||||
|
* self.evaluations_completes
|
||||||
|
).reshape(-1, 1)
|
||||||
|
evals_poids = evals_poids_df.values * evals_coefs
|
||||||
|
# -> evals_poids shape : (nb_evals, nb_ues)
|
||||||
|
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||||
|
# Remplace les notes ATT, EXC, ABS, NaN par zéro et mets les notes sur 20:
|
||||||
|
evals_notes = np.where(
|
||||||
|
self.evals_notes.values > scu.NOTES_ABSENCE, self.evals_notes.values, 0.0
|
||||||
|
) / [e.note_max / 20.0 for e in moduleimpl.evaluations]
|
||||||
|
# Les poids des évals pour les étudiant: là où il a des notes non neutralisées
|
||||||
|
# (ABS n'est pas neutralisée, mais ATTENTE et NEUTRALISE oui)
|
||||||
|
# Note: les NaN sont remplacés par des 0 dans evals_notes
|
||||||
|
# et dans dans evals_poids_etuds
|
||||||
|
# (rappel: la comparaison est toujours false face à un NaN)
|
||||||
|
# shape: (nb_etuds, nb_evals, nb_ues)
|
||||||
|
poids_stacked = np.stack([evals_poids] * nb_etuds)
|
||||||
|
evals_poids_etuds = np.where(
|
||||||
|
np.stack([self.evals_notes.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
||||||
|
poids_stacked,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
# Calcule la moyenne pondérée sur les notes disponibles:
|
||||||
|
evals_notes_stacked = np.stack([evals_notes] * nb_ues, axis=2)
|
||||||
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
|
etuds_moy_module = np.sum(
|
||||||
|
evals_poids_etuds * evals_notes_stacked, axis=1
|
||||||
|
) / np.sum(evals_poids_etuds, axis=1)
|
||||||
|
self.etuds_moy_module = pd.DataFrame(
|
||||||
|
etuds_moy_module,
|
||||||
|
index=self.evals_notes.index,
|
||||||
|
columns=evals_poids_df.columns,
|
||||||
|
)
|
||||||
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
|
||||||
|
def load_evaluations_poids(
|
||||||
moduleimpl_id: int, default_poids=1.0
|
moduleimpl_id: int, default_poids=1.0
|
||||||
) -> tuple[pd.DataFrame, list]:
|
) -> tuple[pd.DataFrame, list]:
|
||||||
"""Charge poids des évaluations d'un module et retourne un dataframe
|
"""Charge poids des évaluations d'un module et retourne un dataframe
|
||||||
@ -55,23 +255,25 @@ def df_load_evaluations_poids(
|
|||||||
ues = modimpl.formsemestre.query_ues(with_sport=False).all()
|
ues = modimpl.formsemestre.query_ues(with_sport=False).all()
|
||||||
ue_ids = [ue.id for ue in ues]
|
ue_ids = [ue.id for ue in ues]
|
||||||
evaluation_ids = [evaluation.id for evaluation in evaluations]
|
evaluation_ids = [evaluation.id for evaluation in evaluations]
|
||||||
df = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
||||||
for eval_poids in EvaluationUEPoids.query.join(
|
for ue_poids in EvaluationUEPoids.query.join(
|
||||||
EvaluationUEPoids.evaluation
|
EvaluationUEPoids.evaluation
|
||||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||||
df[eval_poids.ue_id][eval_poids.evaluation_id] = eval_poids.poids
|
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
||||||
if default_poids is not None:
|
if default_poids is not None:
|
||||||
df.fillna(value=default_poids, inplace=True)
|
evals_poids.fillna(value=default_poids, inplace=True)
|
||||||
return df, ues
|
return evals_poids, ues
|
||||||
|
|
||||||
|
|
||||||
def check_moduleimpl_conformity(
|
def moduleimpl_is_conforme(
|
||||||
moduleimpl, evals_poids: pd.DataFrame, modules_coefficients: pd.DataFrame
|
moduleimpl, evals_poids: pd.DataFrame, modules_coefficients: pd.DataFrame
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Vérifie que les évaluations de ce moduleimpl sont bien conformes
|
"""Vérifie que les évaluations de ce moduleimpl sont bien conformes
|
||||||
au PN.
|
au PN.
|
||||||
Un module est dit *conforme* si et seulement si la somme des poids de ses
|
Un module est dit *conforme* si et seulement si la somme des poids de ses
|
||||||
évaluations vers une UE de coefficient non nul est non nulle.
|
évaluations vers une UE de coefficient non nul est non nulle.
|
||||||
|
|
||||||
|
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||||
"""
|
"""
|
||||||
nb_evals, nb_ues = evals_poids.shape
|
nb_evals, nb_ues = evals_poids.shape
|
||||||
if nb_evals == 0:
|
if nb_evals == 0:
|
||||||
@ -79,160 +281,10 @@ def check_moduleimpl_conformity(
|
|||||||
if nb_ues == 0:
|
if nb_ues == 0:
|
||||||
return False # situation absurde (pas d'UE)
|
return False # situation absurde (pas d'UE)
|
||||||
if len(modules_coefficients) != nb_ues:
|
if len(modules_coefficients) != nb_ues:
|
||||||
raise ValueError("check_moduleimpl_conformity: nb ue incoherent")
|
raise ValueError("moduleimpl_is_conforme: nb ue incoherent")
|
||||||
module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0
|
module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0
|
||||||
check = all(
|
check = all(
|
||||||
(modules_coefficients[moduleimpl.module.id].to_numpy() != 0)
|
(modules_coefficients[moduleimpl.module_id].to_numpy() != 0)
|
||||||
== module_evals_poids
|
== module_evals_poids
|
||||||
)
|
)
|
||||||
return check
|
return check
|
||||||
|
|
||||||
|
|
||||||
def df_load_modimpl_notes(moduleimpl_id: int) -> tuple:
|
|
||||||
"""Construit un dataframe avec toutes les notes de toutes les évaluations du module.
|
|
||||||
colonnes: le nom de la colonne est l'evaluation_id (int)
|
|
||||||
index (lignes): etudid (int)
|
|
||||||
|
|
||||||
Résultat: (evals_notes, liste de évaluations du moduleimpl,
|
|
||||||
liste de booleens indiquant si l'évaluation est "complete")
|
|
||||||
|
|
||||||
L'ensemble des étudiants est celui des inscrits au SEMESTRE.
|
|
||||||
|
|
||||||
Les notes renvoyées sont "brutes" (séries de floats) et peuvent prendre les valeurs:
|
|
||||||
note : float (valeur enregistrée brute, non normalisée sur 20)
|
|
||||||
pas de note: NaN (rien en bd, ou étudiant non inscrit au module)
|
|
||||||
absent: NOTES_ABSENCE (NULL en bd)
|
|
||||||
excusé: NOTES_NEUTRALISE (voir sco_utils)
|
|
||||||
attente: NOTES_ATTENTE
|
|
||||||
|
|
||||||
L'évaluation "complete" (prise en compte dans les calculs) si:
|
|
||||||
- soit tous les étudiants inscrits au module ont des notes
|
|
||||||
- soit elle a été déclarée "à prise ne compte immédiate" (publish_incomplete)
|
|
||||||
|
|
||||||
N'utilise pas de cache ScoDoc.
|
|
||||||
"""
|
|
||||||
# L'index du dataframe est la liste des étudiants inscrits au semestre,
|
|
||||||
# sans les démissionnaires
|
|
||||||
etudids = [
|
|
||||||
e.etudid
|
|
||||||
for e in ModuleImpl.query.get(moduleimpl_id).formsemestre.get_inscrits(
|
|
||||||
include_dem=False
|
|
||||||
)
|
|
||||||
]
|
|
||||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
|
||||||
# --- Calcul nombre d'inscrits pour détermnier si évaluation "complete":
|
|
||||||
if evaluations:
|
|
||||||
# on prend les inscrits au module ET au semestre (donc sans démissionnaires)
|
|
||||||
inscrits_module = {
|
|
||||||
ins.etud.id for ins in evaluations[0].moduleimpl.inscriptions
|
|
||||||
}.intersection(etudids)
|
|
||||||
nb_inscrits_module = len(inscrits_module)
|
|
||||||
else:
|
|
||||||
nb_inscrits_module = 0
|
|
||||||
# empty df with all students:
|
|
||||||
evals_notes = pd.DataFrame(index=etudids, dtype=float)
|
|
||||||
evaluations_completes = []
|
|
||||||
for evaluation in evaluations:
|
|
||||||
eval_df = pd.read_sql_query(
|
|
||||||
"""SELECT n.etudid, n.value AS "%(evaluation_id)s"
|
|
||||||
FROM notes_notes n, notes_moduleimpl_inscription i
|
|
||||||
WHERE evaluation_id=%(evaluation_id)s
|
|
||||||
AND n.etudid = i.etudid
|
|
||||||
AND i.moduleimpl_id = %(moduleimpl_id)s
|
|
||||||
ORDER BY n.etudid
|
|
||||||
""",
|
|
||||||
db.engine,
|
|
||||||
params={
|
|
||||||
"evaluation_id": evaluation.id,
|
|
||||||
"moduleimpl_id": evaluation.moduleimpl.id,
|
|
||||||
},
|
|
||||||
index_col="etudid",
|
|
||||||
)
|
|
||||||
eval_df[str(evaluation.id)] = pd.to_numeric(eval_df[str(evaluation.id)])
|
|
||||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
|
||||||
is_complete = (
|
|
||||||
len(set(eval_df.index).intersection(etudids)) == nb_inscrits_module
|
|
||||||
) or evaluation.publish_incomplete
|
|
||||||
evaluations_completes.append(is_complete)
|
|
||||||
# NULL en base => ABS (= -999)
|
|
||||||
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
|
||||||
# Ce merge met à NULL les élements non présents
|
|
||||||
# (notes non saisies ou etuds non inscrits au module):
|
|
||||||
evals_notes = evals_notes.merge(
|
|
||||||
eval_df, how="left", left_index=True, right_index=True
|
|
||||||
)
|
|
||||||
# Force columns names to integers (evaluation ids)
|
|
||||||
evals_notes.columns = pd.Int64Index(
|
|
||||||
[int(x) for x in evals_notes.columns], dtype="int64"
|
|
||||||
)
|
|
||||||
return evals_notes, evaluations, evaluations_completes
|
|
||||||
|
|
||||||
|
|
||||||
def compute_module_moy(
|
|
||||||
evals_notes_df: pd.DataFrame,
|
|
||||||
evals_poids_df: pd.DataFrame,
|
|
||||||
evaluations: list,
|
|
||||||
evaluations_completes: list,
|
|
||||||
) -> pd.DataFrame:
|
|
||||||
"""Calcule les moyennes des étudiants dans ce module
|
|
||||||
|
|
||||||
- evals_notes : DataFrame, colonnes: EVALS, Lignes: etudid
|
|
||||||
valeur: notes brutes, float ou NOTES_ATTENTE, NOTES_NEUTRALISE,
|
|
||||||
NOTES_ABSENCE.
|
|
||||||
Les NaN désignent les notes manquantes (non saisies).
|
|
||||||
|
|
||||||
- evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
|
||||||
|
|
||||||
- evaluations: séquence d'évaluations (utilisées pour le coef et
|
|
||||||
le barème)
|
|
||||||
|
|
||||||
- evaluations_completes: séquence de booléens indiquant les
|
|
||||||
évals à prendre en compte.
|
|
||||||
|
|
||||||
Résultat: DataFrame, colonnes UE, lignes etud
|
|
||||||
= la note de l'étudiant dans chaque UE pour ce module.
|
|
||||||
ou NaN si les évaluations (dans lesquelles l'étudiant à des notes)
|
|
||||||
ne donnent pas de coef vers cette UE.
|
|
||||||
"""
|
|
||||||
nb_etuds, nb_evals = evals_notes_df.shape
|
|
||||||
nb_ues = evals_poids_df.shape[1]
|
|
||||||
assert evals_poids_df.shape[0] == nb_evals # compat notes/poids
|
|
||||||
if nb_etuds == 0:
|
|
||||||
return pd.DataFrame(index=[], columns=evals_poids_df.columns)
|
|
||||||
# Coefficients des évaluations, met à zéro ceux des évals incomplètes:
|
|
||||||
evals_coefs = (
|
|
||||||
np.array(
|
|
||||||
[e.coefficient for e in evaluations],
|
|
||||||
dtype=float,
|
|
||||||
)
|
|
||||||
* evaluations_completes
|
|
||||||
).reshape(-1, 1)
|
|
||||||
evals_poids = evals_poids_df.values * evals_coefs
|
|
||||||
# -> evals_poids shape : (nb_evals, nb_ues)
|
|
||||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
|
||||||
# Remplace les notes ATT, EXC, ABS, NaN par zéro et mets les notes sur 20:
|
|
||||||
evals_notes = np.where(
|
|
||||||
evals_notes_df.values > scu.NOTES_ABSENCE, evals_notes_df.values, 0.0
|
|
||||||
) / [e.note_max / 20.0 for e in evaluations]
|
|
||||||
# Les poids des évals pour les étudiant: là où il a des notes non neutralisées
|
|
||||||
# (ABS n'est pas neutralisée, mais ATTENTE et NEUTRALISE oui)
|
|
||||||
# Note: les NaN sont remplacés par des 0 dans evals_notes
|
|
||||||
# et dans dans evals_poids_etuds
|
|
||||||
# (rappel: la comparaison est toujours false face à un NaN)
|
|
||||||
# shape: (nb_etuds, nb_evals, nb_ues)
|
|
||||||
poids_stacked = np.stack([evals_poids] * nb_etuds)
|
|
||||||
evals_poids_etuds = np.where(
|
|
||||||
np.stack([evals_notes_df.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
|
||||||
poids_stacked,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
# Calcule la moyenne pondérée sur les notes disponibles:
|
|
||||||
evals_notes_stacked = np.stack([evals_notes] * nb_ues, axis=2)
|
|
||||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
|
||||||
etuds_moy_module = np.sum(
|
|
||||||
evals_poids_etuds * evals_notes_stacked, axis=1
|
|
||||||
) / np.sum(evals_poids_etuds, axis=1)
|
|
||||||
etuds_moy_module_df = pd.DataFrame(
|
|
||||||
etuds_moy_module, index=evals_notes_df.index, columns=evals_poids_df.columns
|
|
||||||
)
|
|
||||||
return etuds_moy_module_df
|
|
||||||
|
@ -34,7 +34,6 @@ from app import db
|
|||||||
from app import models
|
from app import models
|
||||||
from app.models import UniteEns, Module, ModuleImpl, ModuleUECoef
|
from app.models import UniteEns, Module, ModuleImpl, ModuleUECoef
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
from app.models.formsemestre import FormSemestre
|
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
|
|
||||||
|
|
||||||
@ -134,34 +133,21 @@ def notes_sem_load_cube(formsemestre):
|
|||||||
Resultat:
|
Resultat:
|
||||||
sem_cube : ndarray (etuds x modimpls x UEs)
|
sem_cube : ndarray (etuds x modimpls x UEs)
|
||||||
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
||||||
modimpls_evals_notes dict { modimpl.id : evals_notes }
|
modimpls_results dict { modimpl.id : ModuleImplResultsAPC }
|
||||||
modimpls_evaluations dict { modimpl.id : liste des évaluations }
|
|
||||||
modimpls_evaluations_complete: {modimpl_id : liste de booleens (complete/non)}
|
|
||||||
"""
|
"""
|
||||||
|
modimpls_results = {}
|
||||||
modimpls_evals_poids = {}
|
modimpls_evals_poids = {}
|
||||||
modimpls_evals_notes = {}
|
|
||||||
modimpls_evaluations = {}
|
|
||||||
modimpls_evaluations_complete = {}
|
|
||||||
modimpls_notes = []
|
modimpls_notes = []
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls:
|
||||||
evals_notes, evaluations, evaluations_completes = moy_mod.df_load_modimpl_notes(
|
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
||||||
modimpl.id
|
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
||||||
)
|
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(modimpl.id)
|
modimpls_results[modimpl.id] = mod_results
|
||||||
etuds_moy_module = moy_mod.compute_module_moy(
|
|
||||||
evals_notes, evals_poids, evaluations, evaluations_completes
|
|
||||||
)
|
|
||||||
modimpls_evals_poids[modimpl.id] = evals_poids
|
|
||||||
modimpls_evals_notes[modimpl.id] = evals_notes
|
|
||||||
modimpls_evaluations[modimpl.id] = evaluations
|
|
||||||
modimpls_evaluations_complete[modimpl.id] = evaluations_completes
|
|
||||||
modimpls_notes.append(etuds_moy_module)
|
modimpls_notes.append(etuds_moy_module)
|
||||||
return (
|
return (
|
||||||
notes_sem_assemble_cube(modimpls_notes),
|
notes_sem_assemble_cube(modimpls_notes),
|
||||||
modimpls_evals_poids,
|
modimpls_evals_poids,
|
||||||
modimpls_evals_notes,
|
modimpls_results,
|
||||||
modimpls_evaluations,
|
|
||||||
modimpls_evaluations_complete,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ from collections import defaultdict
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from app.comp.aux import StatsMoyenne
|
||||||
|
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 ResultatsSemestreCache
|
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
@ -28,14 +30,20 @@ class ResultatsSemestre:
|
|||||||
"modimpl_coefs_df",
|
"modimpl_coefs_df",
|
||||||
"etud_moy_ue",
|
"etud_moy_ue",
|
||||||
"modimpls_evals_poids",
|
"modimpls_evals_poids",
|
||||||
"modimpls_evals_notes",
|
"modimpls_results",
|
||||||
"etud_moy_gen",
|
"etud_moy_gen",
|
||||||
"etud_moy_gen_ranks",
|
"etud_moy_gen_ranks",
|
||||||
"modimpls_evaluations_complete",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, formsemestre):
|
def __init__(self, formsemestre):
|
||||||
self.formsemestre = formsemestre
|
self.formsemestre = formsemestre
|
||||||
|
# BUT ou standard ? (apc == "approche par compétences")
|
||||||
|
self.is_apc = formsemestre.formation.is_apc()
|
||||||
|
# Attributs "virtuels", définis pas les sous-classes
|
||||||
|
# ResultatsSemestreBUT ou ResultatsSemestreStd
|
||||||
|
self.etud_moy_ue = {}
|
||||||
|
self.etud_moy_gen = {}
|
||||||
|
self.etud_moy_gen_ranks = {}
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
def load_cached(self) -> bool:
|
def load_cached(self) -> bool:
|
||||||
@ -49,7 +57,6 @@ class ResultatsSemestre:
|
|||||||
|
|
||||||
def store(self):
|
def store(self):
|
||||||
"Cache our data"
|
"Cache our data"
|
||||||
"Cache our dataframes"
|
|
||||||
ResultatsSemestreCache.set(
|
ResultatsSemestreCache.set(
|
||||||
self.formsemestre.id,
|
self.formsemestre.id,
|
||||||
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
||||||
@ -58,7 +65,7 @@ class ResultatsSemestre:
|
|||||||
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"
|
||||||
# voir ce qui est chargé / calculé ici et dans les sous-classes
|
# voir ce qui est chargé / calculé ici et dans les sous-classes
|
||||||
TODO
|
raise NotImplementedError()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def etuds(self):
|
def etuds(self):
|
||||||
@ -78,9 +85,22 @@ class ResultatsSemestre:
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def modimpls(self):
|
def modimpls(self):
|
||||||
"Liste des modimpls du semestre (triée par numéro de module)"
|
"""Liste des modimpls du semestre
|
||||||
|
- triée par numéro de module en APC
|
||||||
|
- triée par numéros d'UE/matières/modules pour les formations standard.
|
||||||
|
"""
|
||||||
modimpls = self.formsemestre.modimpls.all()
|
modimpls = self.formsemestre.modimpls.all()
|
||||||
modimpls.sort(key=lambda m: m.module.numero)
|
if self.is_apc:
|
||||||
|
modimpls.sort(key=lambda m: (m.module.numero, m.module.code))
|
||||||
|
else:
|
||||||
|
modimpls.sort(
|
||||||
|
key=lambda m: (
|
||||||
|
m.module.ue.numero,
|
||||||
|
m.module.matiere.numero,
|
||||||
|
m.module.numero,
|
||||||
|
m.module.code,
|
||||||
|
)
|
||||||
|
)
|
||||||
return modimpls
|
return modimpls
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@ -96,32 +116,6 @@ class ResultatsSemestre:
|
|||||||
return [m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE]
|
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
|
# Pour raccorder le code des anciens codes qui attendent une NoteTable
|
||||||
class NotesTableCompat(ResultatsSemestre):
|
class NotesTableCompat(ResultatsSemestre):
|
||||||
"""Implementation partielle de NotesTable WIP TODO
|
"""Implementation partielle de NotesTable WIP TODO
|
||||||
@ -158,11 +152,22 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
ues.append(d)
|
ues.append(d)
|
||||||
return ues
|
return ues
|
||||||
|
|
||||||
def get_modimpls(self):
|
def get_modimpls_dict(self, ue_id=None):
|
||||||
return [m.to_dict() for m in self.results.modimpls]
|
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
||||||
|
triés par numéros (selon le type de formation)
|
||||||
|
"""
|
||||||
|
if ue_id is None:
|
||||||
|
return [m.to_dict() for m in self.modimpls]
|
||||||
|
else:
|
||||||
|
return [m.to_dict() for m in self.modimpls if m.module.ue.id == ue_id]
|
||||||
|
|
||||||
def get_etud_moy_gen(self, etudid):
|
def get_etud_moy_gen(self, etudid): # -> float | str
|
||||||
return self.results.etud_moy_gen[etudid]
|
"""Moyenne générale de cet etudiant dans ce semestre.
|
||||||
|
Prend en compte les UE capitalisées. (TODO)
|
||||||
|
Si apc, moyenne indicative.
|
||||||
|
Si pas de notes: 'NA'
|
||||||
|
"""
|
||||||
|
return self.etud_moy_gen[etudid]
|
||||||
|
|
||||||
def get_moduleimpls_attente(self):
|
def get_moduleimpls_attente(self):
|
||||||
return [] # XXX TODO
|
return [] # XXX TODO
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
et données rattachées (adresses, annotations, ...)
|
et données rattachées (adresses, annotations, ...)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import g, url_for
|
from functools import cached_property
|
||||||
|
from flask import abort, url_for
|
||||||
|
from flask import g, request
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import models
|
||||||
@ -53,14 +55,24 @@ class Identite(db.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Etud {self.id} {self.nom} {self.prenom}>"
|
return f"<Etud {self.id} {self.nom} {self.prenom}>"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_request(cls, etudid=None, code_nip=None):
|
||||||
|
"""Etudiant à partir de l'etudid ou du code_nip, soit
|
||||||
|
passés en argument soit retrouvés directement dans la requête web.
|
||||||
|
Erreur 404 si inexistant.
|
||||||
|
"""
|
||||||
|
args = make_etud_args(etudid=etudid, code_nip=code_nip)
|
||||||
|
return Identite.query.filter_by(**args).first_or_404()
|
||||||
|
|
||||||
|
@property
|
||||||
def civilite_str(self):
|
def civilite_str(self):
|
||||||
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
||||||
personnes ne souhaitant pas d'affichage).
|
personnes ne souhaitant pas d'affichage).
|
||||||
"""
|
"""
|
||||||
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]
|
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]
|
||||||
|
|
||||||
def nom_disp(self):
|
def nom_disp(self) -> str:
|
||||||
"nom à afficher"
|
"Nom à afficher"
|
||||||
if self.nom_usuel:
|
if self.nom_usuel:
|
||||||
return (
|
return (
|
||||||
(self.nom_usuel + " (" + self.nom + ")") if self.nom else self.nom_usuel
|
(self.nom_usuel + " (" + self.nom + ")") if self.nom else self.nom_usuel
|
||||||
@ -68,8 +80,33 @@ class Identite(db.Model):
|
|||||||
else:
|
else:
|
||||||
return self.nom
|
return self.nom
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def nomprenom(self, reverse=False) -> str:
|
||||||
|
"""Civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
||||||
|
Si reverse, "Dupont Pierre", sans civilité.
|
||||||
|
"""
|
||||||
|
nom = self.nom_usuel or self.nom
|
||||||
|
prenom = self.prenom_str
|
||||||
|
if reverse:
|
||||||
|
fields = (nom, prenom)
|
||||||
|
else:
|
||||||
|
fields = (self.civilite_str, prenom, nom)
|
||||||
|
return " ".join([x for x in fields if x])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prenom_str(self):
|
||||||
|
"""Prénom à afficher. Par exemple: "Jean-Christophe" """
|
||||||
|
if not self.prenom:
|
||||||
|
return ""
|
||||||
|
frags = self.prenom.split()
|
||||||
|
r = []
|
||||||
|
for frag in frags:
|
||||||
|
fields = frag.split("-")
|
||||||
|
r.append("-".join([x.lower().capitalize() for x in fields]))
|
||||||
|
return " ".join(r)
|
||||||
|
|
||||||
def get_first_email(self, field="email") -> str:
|
def get_first_email(self, field="email") -> str:
|
||||||
"le mail associé à la première adrese de l'étudiant, ou None"
|
"Le mail associé à la première adrese de l'étudiant, ou None"
|
||||||
return self.adresses[0].email or None if self.adresses.count() > 0 else None
|
return self.adresses[0].email or None if self.adresses.count() > 0 else None
|
||||||
|
|
||||||
def to_dict_bul(self, include_urls=True):
|
def to_dict_bul(self, include_urls=True):
|
||||||
@ -120,6 +157,42 @@ class Identite(db.Model):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def make_etud_args(
|
||||||
|
etudid=None, code_nip=None, use_request=True, raise_exc=False, abort_404=True
|
||||||
|
) -> dict:
|
||||||
|
"""forme args dict pour requete recherche etudiant
|
||||||
|
On peut specifier etudid
|
||||||
|
ou bien (si use_request) cherche dans la requete http: etudid, code_nip, code_ine
|
||||||
|
(dans cet ordre).
|
||||||
|
|
||||||
|
Résultat: dict avec soit "etudid", soit "code_nip", soit "code_ine"
|
||||||
|
"""
|
||||||
|
args = None
|
||||||
|
if etudid:
|
||||||
|
args = {"etudid": etudid}
|
||||||
|
elif code_nip:
|
||||||
|
args = {"code_nip": code_nip}
|
||||||
|
elif use_request: # use form from current request (Flask global)
|
||||||
|
if request.method == "POST":
|
||||||
|
vals = request.form
|
||||||
|
elif request.method == "GET":
|
||||||
|
vals = request.args
|
||||||
|
else:
|
||||||
|
vals = {}
|
||||||
|
if "etudid" in vals:
|
||||||
|
args = {"etudid": int(vals["etudid"])}
|
||||||
|
elif "code_nip" in vals:
|
||||||
|
args = {"code_nip": str(vals["code_nip"])}
|
||||||
|
elif "code_ine" in vals:
|
||||||
|
args = {"code_ine": str(vals["code_ine"])}
|
||||||
|
if not args:
|
||||||
|
if abort_404:
|
||||||
|
abort(404, "pas d'étudiant sélectionné")
|
||||||
|
elif raise_exc:
|
||||||
|
raise ValueError("make_etud_args: pas d'étudiant sélectionné !")
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
class Adresse(db.Model):
|
class Adresse(db.Model):
|
||||||
"""Adresse d'un étudiant
|
"""Adresse d'un étudiant
|
||||||
(le modèle permet plusieurs adresses, mais l'UI n'en gère qu'une seule)
|
(le modèle permet plusieurs adresses, mais l'UI n'en gère qu'une seule)
|
||||||
|
@ -50,7 +50,7 @@ class ModuleImpl(db.Model):
|
|||||||
if evaluations_poids is None:
|
if evaluations_poids is None:
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
|
|
||||||
evaluations_poids, _ = moy_mod.df_load_evaluations_poids(self.id)
|
evaluations_poids, _ = moy_mod.load_evaluations_poids(self.id)
|
||||||
df_cache.EvaluationsPoidsCache.set(self.id, evaluations_poids)
|
df_cache.EvaluationsPoidsCache.set(self.id, evaluations_poids)
|
||||||
return evaluations_poids
|
return evaluations_poids
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ class ModuleImpl(db.Model):
|
|||||||
return True
|
return True
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
|
|
||||||
return moy_mod.check_moduleimpl_conformity(
|
return moy_mod.moduleimpl_is_conforme(
|
||||||
self,
|
self,
|
||||||
self.get_evaluations_poids(),
|
self.get_evaluations_poids(),
|
||||||
self.module.formation.get_module_coefs(self.module.semestre_id),
|
self.module.formation.get_module_coefs(self.module.semestre_id),
|
||||||
|
@ -296,7 +296,7 @@ class NotesTable:
|
|||||||
for ue in self._ues:
|
for ue in self._ues:
|
||||||
is_cap[ue["ue_id"]] = ue_status[ue["ue_id"]]["is_capitalized"]
|
is_cap[ue["ue_id"]] = ue_status[ue["ue_id"]]["is_capitalized"]
|
||||||
|
|
||||||
for modimpl in self.get_modimpls():
|
for modimpl in self.get_modimpls_dict():
|
||||||
val = self.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
val = self.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
||||||
if is_cap[modimpl["module"]["ue_id"]]:
|
if is_cap[modimpl["module"]["ue_id"]]:
|
||||||
t.append("-c-")
|
t.append("-c-")
|
||||||
@ -428,8 +428,8 @@ class NotesTable:
|
|||||||
else:
|
else:
|
||||||
return [ue for ue in self._ues if ue["type"] != UE_SPORT]
|
return [ue for ue in self._ues if ue["type"] != UE_SPORT]
|
||||||
|
|
||||||
def get_modimpls(self, ue_id=None):
|
def get_modimpls_dict(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."
|
||||||
if ue_id is None:
|
if ue_id is None:
|
||||||
r = self._modimpls
|
r = self._modimpls
|
||||||
else:
|
else:
|
||||||
@ -564,7 +564,7 @@ class NotesTable:
|
|||||||
Si non inscrit, moy == 'NI' et sum_coefs==0
|
Si non inscrit, moy == 'NI' et sum_coefs==0
|
||||||
"""
|
"""
|
||||||
assert ue_id
|
assert ue_id
|
||||||
modimpls = self.get_modimpls(ue_id)
|
modimpls = self.get_modimpls_dict(ue_id)
|
||||||
nb_notes = 0 # dans cette UE
|
nb_notes = 0 # dans cette UE
|
||||||
sum_notes = 0.0
|
sum_notes = 0.0
|
||||||
sum_coefs = 0.0
|
sum_coefs = 0.0
|
||||||
@ -921,7 +921,7 @@ class NotesTable:
|
|||||||
|
|
||||||
return infos
|
return infos
|
||||||
|
|
||||||
def get_etud_moy_gen(self, etudid):
|
def get_etud_moy_gen(self, etudid): # -> float | str
|
||||||
"""Moyenne generale de cet etudiant dans ce semestre.
|
"""Moyenne generale de cet etudiant dans ce semestre.
|
||||||
Prend en compte les UE capitalisées.
|
Prend en compte les UE capitalisées.
|
||||||
Si pas de notes: 'NA'
|
Si pas de notes: 'NA'
|
||||||
|
@ -32,6 +32,8 @@ import datetime
|
|||||||
|
|
||||||
from flask import url_for, g, request, abort
|
from flask import url_for, g, request, abort
|
||||||
|
|
||||||
|
from app import log
|
||||||
|
from app.models import Identite
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
@ -46,7 +48,6 @@ from app.scodoc import sco_groups
|
|||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
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 import log
|
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
@ -71,8 +72,8 @@ def doSignaleAbsence(
|
|||||||
etudid: etudiant concerné. Si non spécifié, cherche dans
|
etudid: etudiant concerné. Si non spécifié, cherche dans
|
||||||
les paramètres de la requête courante.
|
les paramètres de la requête courante.
|
||||||
"""
|
"""
|
||||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
etud = Identite.from_request(etudid)
|
||||||
etudid = etud["etudid"]
|
|
||||||
if not moduleimpl_id:
|
if not moduleimpl_id:
|
||||||
moduleimpl_id = None
|
moduleimpl_id = None
|
||||||
description_abs = description
|
description_abs = description
|
||||||
@ -82,7 +83,7 @@ def doSignaleAbsence(
|
|||||||
for jour in dates:
|
for jour in dates:
|
||||||
if demijournee == 2:
|
if demijournee == 2:
|
||||||
sco_abs.add_absence(
|
sco_abs.add_absence(
|
||||||
etudid,
|
etud.id,
|
||||||
jour,
|
jour,
|
||||||
False,
|
False,
|
||||||
estjust,
|
estjust,
|
||||||
@ -90,7 +91,7 @@ def doSignaleAbsence(
|
|||||||
moduleimpl_id,
|
moduleimpl_id,
|
||||||
)
|
)
|
||||||
sco_abs.add_absence(
|
sco_abs.add_absence(
|
||||||
etudid,
|
etud.id,
|
||||||
jour,
|
jour,
|
||||||
True,
|
True,
|
||||||
estjust,
|
estjust,
|
||||||
@ -100,7 +101,7 @@ def doSignaleAbsence(
|
|||||||
nbadded += 2
|
nbadded += 2
|
||||||
else:
|
else:
|
||||||
sco_abs.add_absence(
|
sco_abs.add_absence(
|
||||||
etudid,
|
etud.id,
|
||||||
jour,
|
jour,
|
||||||
demijournee,
|
demijournee,
|
||||||
estjust,
|
estjust,
|
||||||
@ -113,27 +114,27 @@ def doSignaleAbsence(
|
|||||||
J = ""
|
J = ""
|
||||||
else:
|
else:
|
||||||
J = "NON "
|
J = "NON "
|
||||||
M = ""
|
indication_module = ""
|
||||||
if moduleimpl_id and moduleimpl_id != "NULL":
|
if moduleimpl_id and moduleimpl_id != "NULL":
|
||||||
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_stat_dict()
|
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_dict(ue_id=ue["ue_id"])
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if modimpl["moduleimpl_id"] == moduleimpl_id:
|
if modimpl["moduleimpl_id"] == moduleimpl_id:
|
||||||
M = "dans le module %s" % modimpl["module"]["code"]
|
indication_module = "dans le module %s" % modimpl["module"]["code"]
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title="Signalement d'une absence pour %(nomprenom)s" % etud,
|
page_title=f"Signalement d'une absence pour {etud.nomprenom}",
|
||||||
),
|
),
|
||||||
"""<h2>Signalement d'absences</h2>""",
|
"""<h2>Signalement d'absences</h2>""",
|
||||||
]
|
]
|
||||||
if dates:
|
if dates:
|
||||||
H.append(
|
H.append(
|
||||||
"""<p>Ajout de %d absences <b>%sjustifiées</b> du %s au %s %s</p>"""
|
"""<p>Ajout de %d absences <b>%sjustifiées</b> du %s au %s %s</p>"""
|
||||||
% (nbadded, J, datedebut, datefin, M)
|
% (nbadded, J, datedebut, datefin, indication_module)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append(
|
H.append(
|
||||||
@ -142,11 +143,18 @@ def doSignaleAbsence(
|
|||||||
)
|
)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
"""<ul><li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Autre absence pour <b>%(nomprenom)s</b></a></li>
|
f"""<ul>
|
||||||
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
|
<li><a href="{url_for("absences.SignaleAbsenceEtud",
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||||
|
)}">Autre absence pour <b>{etud.nomprenom}</b></a>
|
||||||
|
</li>
|
||||||
|
<li><a href="{url_for("absences.CalAbs",
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||||
|
)}">Calendrier de ses absences</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr>"""
|
<hr>
|
||||||
% etud
|
"""
|
||||||
)
|
)
|
||||||
H.append(sco_find_etud.form_search_etud())
|
H.append(sco_find_etud.form_search_etud())
|
||||||
H.append(html_sco_header.sco_footer())
|
H.append(html_sco_header.sco_footer())
|
||||||
@ -200,7 +208,7 @@ def SignaleAbsenceEtud(): # etudid implied
|
|||||||
menu_module += """<option value="" selected>(Module)</option>"""
|
menu_module += """<option value="" selected>(Module)</option>"""
|
||||||
|
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
modimpls = nt.get_modimpls(ue_id=ue["ue_id"])
|
modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
menu_module += (
|
menu_module += (
|
||||||
"""<option value="%(modimpl_id)s">%(modname)s</option>\n"""
|
"""<option value="%(modimpl_id)s">%(modname)s</option>\n"""
|
||||||
|
@ -456,7 +456,7 @@ class ApoEtud(dict):
|
|||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
|
|
||||||
# Elements Modules
|
# Elements Modules
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
module_code_found = False
|
module_code_found = False
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if code in modimpl["module"]["code_apogee"].split(","):
|
if code in modimpl["module"]["code_apogee"].split(","):
|
||||||
@ -978,7 +978,7 @@ class ApoData(object):
|
|||||||
s.add(code)
|
s.add(code)
|
||||||
continue
|
continue
|
||||||
# associé à un module:
|
# associé à un module:
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if code in modimpl["module"]["code_apogee"].split(","):
|
if code in modimpl["module"]["code_apogee"].split(","):
|
||||||
s.add(code)
|
s.add(code)
|
||||||
|
@ -219,7 +219,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
|
|
||||||
# --- Notes
|
# --- Notes
|
||||||
ues = nt.get_ues_stat_dict()
|
ues = nt.get_ues_stat_dict()
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
moy_gen = nt.get_etud_moy_gen(etudid)
|
moy_gen = nt.get_etud_moy_gen(etudid)
|
||||||
I["nb_inscrits"] = len(nt.etud_moy_gen_ranks)
|
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)
|
||||||
@ -352,7 +352,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
etudid,
|
etudid,
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
ue_status["capitalized_ue_id"],
|
ue_status["capitalized_ue_id"],
|
||||||
nt_cap.get_modimpls(),
|
nt_cap.get_modimpls_dict(),
|
||||||
nt_cap,
|
nt_cap,
|
||||||
version,
|
version,
|
||||||
)
|
)
|
||||||
|
@ -154,7 +154,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
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_stat_dict()
|
ues = nt.get_ues_stat_dict()
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
nbetuds = len(nt.etud_moy_gen_ranks)
|
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 (
|
||||||
|
@ -152,7 +152,7 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
|
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
||||||
ues = nt.get_ues_stat_dict()
|
ues = nt.get_ues_stat_dict()
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
nbetuds = len(nt.etud_moy_gen_ranks)
|
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 (
|
||||||
|
@ -38,7 +38,7 @@ from flask_mail import Message
|
|||||||
|
|
||||||
from app import email
|
from app import email
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.models.etudiants import make_etud_args
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||||
@ -87,6 +87,8 @@ def force_uppercase(s):
|
|||||||
def format_nomprenom(etud, reverse=False):
|
def format_nomprenom(etud, reverse=False):
|
||||||
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
||||||
Si reverse, "Dupont Pierre", sans civilité.
|
Si reverse, "Dupont Pierre", sans civilité.
|
||||||
|
|
||||||
|
DEPRECATED: utiliser Identite.nomprenom
|
||||||
"""
|
"""
|
||||||
nom = etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"]
|
nom = etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"]
|
||||||
prenom = format_prenom(etud["prenom"])
|
prenom = format_prenom(etud["prenom"])
|
||||||
@ -99,7 +101,9 @@ def format_nomprenom(etud, reverse=False):
|
|||||||
|
|
||||||
|
|
||||||
def format_prenom(s):
|
def format_prenom(s):
|
||||||
"Formatte prenom etudiant pour affichage"
|
"""Formatte prenom etudiant pour affichage
|
||||||
|
DEPRECATED: utiliser Identite.prenom_str
|
||||||
|
"""
|
||||||
if not s:
|
if not s:
|
||||||
return ""
|
return ""
|
||||||
frags = s.split()
|
frags = s.split()
|
||||||
@ -590,35 +594,6 @@ etudident_edit = _etudidentEditor.edit
|
|||||||
etudident_create = _etudidentEditor.create
|
etudident_create = _etudidentEditor.create
|
||||||
|
|
||||||
|
|
||||||
def make_etud_args(etudid=None, code_nip=None, use_request=True, raise_exc=True):
|
|
||||||
"""forme args dict pour requete recherche etudiant
|
|
||||||
On peut specifier etudid
|
|
||||||
ou bien (si use_request) cherche dans la requete http: etudid, code_nip, code_ine
|
|
||||||
(dans cet ordre).
|
|
||||||
"""
|
|
||||||
args = None
|
|
||||||
if etudid:
|
|
||||||
args = {"etudid": etudid}
|
|
||||||
elif code_nip:
|
|
||||||
args = {"code_nip": code_nip}
|
|
||||||
elif use_request: # use form from current request (Flask global)
|
|
||||||
if request.method == "POST":
|
|
||||||
vals = request.form
|
|
||||||
elif request.method == "GET":
|
|
||||||
vals = request.args
|
|
||||||
else:
|
|
||||||
vals = {}
|
|
||||||
if "etudid" in vals:
|
|
||||||
args = {"etudid": int(vals["etudid"])}
|
|
||||||
elif "code_nip" in vals:
|
|
||||||
args = {"code_nip": str(vals["code_nip"])}
|
|
||||||
elif "code_ine" in vals:
|
|
||||||
args = {"code_ine": str(vals["code_ine"])}
|
|
||||||
if not args and raise_exc:
|
|
||||||
raise ValueError("getEtudInfo: no parameter !")
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def log_unknown_etud():
|
def log_unknown_etud():
|
||||||
"""Log request: cas ou getEtudInfo n'a pas ramene de resultat"""
|
"""Log request: cas ou getEtudInfo n'a pas ramene de resultat"""
|
||||||
etud_args = make_etud_args(raise_exc=False)
|
etud_args = make_etud_args(raise_exc=False)
|
||||||
|
@ -240,7 +240,7 @@ def _make_table_notes(
|
|||||||
if is_apc:
|
if is_apc:
|
||||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
is_conforme = modimpl.check_apc_conformity()
|
is_conforme = modimpl.check_apc_conformity()
|
||||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(moduleimpl_id)
|
evals_poids, ues = moy_mod.load_evaluations_poids(moduleimpl_id)
|
||||||
if not ues:
|
if not ues:
|
||||||
is_apc = False
|
is_apc = False
|
||||||
else:
|
else:
|
||||||
|
@ -36,6 +36,7 @@ from flask_login import current_user
|
|||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.models.etudiants import make_etud_args
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
from app.scodoc import sco_archives_etud
|
from app.scodoc import sco_archives_etud
|
||||||
@ -156,7 +157,7 @@ def ficheEtud(etudid=None):
|
|||||||
# la sidebar est differente s'il y a ou pas un etudid
|
# la sidebar est differente s'il y a ou pas un etudid
|
||||||
# voir html_sidebar.sidebar()
|
# voir html_sidebar.sidebar()
|
||||||
g.etudid = etudid
|
g.etudid = etudid
|
||||||
args = sco_etud.make_etud_args(etudid=etudid)
|
args = make_etud_args(etudid=etudid)
|
||||||
etuds = sco_etud.etudident_list(cnx, args)
|
etuds = sco_etud.etudident_list(cnx, args)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
log("ficheEtud: etudid=%s request.args=%s" % (etudid, request.args))
|
log("ficheEtud: etudid=%s request.args=%s" % (etudid, request.args))
|
||||||
|
@ -75,7 +75,7 @@ def etud_get_poursuite_info(sem, etud):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Moyennes et rang des modules
|
# Moyennes et rang des modules
|
||||||
modimpls = nt.get_modimpls() # recupération des modules
|
modimpls = nt.get_modimpls_dict() # recupération des modules
|
||||||
modules = []
|
modules = []
|
||||||
rangs = []
|
rangs = []
|
||||||
for ue in ues: # on parcourt chaque UE
|
for ue in ues: # on parcourt chaque UE
|
||||||
|
@ -302,10 +302,8 @@ def make_formsemestre_recapcomplet(
|
|||||||
sem = sco_formsemestre.do_formsemestre_list(
|
sem = sco_formsemestre.do_formsemestre_list(
|
||||||
args={"formsemestre_id": formsemestre_id}
|
args={"formsemestre_id": formsemestre_id}
|
||||||
)[0]
|
)[0]
|
||||||
nt = sco_cache.NotesTableCache.get(
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
formsemestre_id
|
modimpls = nt.get_modimpls_dict()
|
||||||
) # > 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_stat_dict() # 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():
|
||||||
|
@ -270,7 +270,7 @@ def get_etud_tagged_modules(etudid, tagname):
|
|||||||
R = []
|
R = []
|
||||||
for sem in etud["sems"]:
|
for sem in etud["sems"]:
|
||||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
tags = module_tag_list(module_id=modimpl["module_id"])
|
tags = module_tag_list(module_id=modimpl["module_id"])
|
||||||
if tagname in tags:
|
if tagname in tags:
|
||||||
|
@ -3,11 +3,9 @@
|
|||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
{# sidebar_common #}
|
{# sidebar_common #}
|
||||||
<a class="scodoc_title" href="{{
|
<a class="scodoc_title" href="{{
|
||||||
url_for("scodoc.index", scodoc_dept=g.scodoc_dept)
|
url_for("scodoc.index", scodoc_dept=g.scodoc_dept) }}">ScoDoc 9.2a</a>
|
||||||
}}">ScoDoc 9.1</a>
|
|
||||||
<div id="authuser"><a id="authuserlink" href="{{
|
<div id="authuser"><a id="authuserlink" href="{{
|
||||||
url_for("users.user_info_page",
|
url_for("users.user_info_page", scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
||||||
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
|
||||||
}}">{{current_user.user_name}}</a>
|
}}">{{current_user.user_name}}</a>
|
||||||
<br /><a id="deconnectlink" href="{{url_for("auth.logout")}}">déconnexion</a>
|
<br /><a id="deconnectlink" href="{{url_for("auth.logout")}}">déconnexion</a>
|
||||||
</div>
|
</div>
|
||||||
@ -19,13 +17,13 @@
|
|||||||
<a href="{{ sco.prefs[" DeptIntranetURL"] }}" class="sidebar">
|
<a href="{{ sco.prefs[" DeptIntranetURL"] }}" class="sidebar">
|
||||||
{{ sco.prefs["DeptIntranetTitle"] }}</a>
|
{{ sco.prefs["DeptIntranetTitle"] }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br />
|
<br>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<h2 class="insidebar">Scolarité</h2>
|
<h2 class="insidebar">Scolarité</h2>
|
||||||
<a href="{{url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br/>
|
<a href="{{url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br>
|
||||||
<a href="{{url_for("notes.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br/>
|
<a href="{{url_for("notes.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br>
|
||||||
<a href="{{url_for("absences.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Absences</a> <br/>
|
<a href="{{url_for("absences.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Absences</a> <br>
|
||||||
|
|
||||||
{% if current_user.has_permission(sco.Permission.ScoUsersAdmin)
|
{% if current_user.has_permission(sco.Permission.ScoUsersAdmin)
|
||||||
or current_user.has_permission(sco.Permission.ScoUsersView)
|
or current_user.has_permission(sco.Permission.ScoUsersView)
|
||||||
@ -34,12 +32,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_user.has_permission(sco.Permission.ScoChangePreferences) %}
|
{% if current_user.has_permission(sco.Permission.ScoChangePreferences) %}
|
||||||
<a href="{{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}}"
|
<a href="{{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Paramétrage</a> <br>
|
||||||
class="sidebar">Paramétrage</a> <br/>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# /sidebar_common #}
|
{# /sidebar_common #}
|
||||||
|
|
||||||
<div class="box-chercheetud">Chercher étudiant:<br/>
|
<div class="box-chercheetud">Chercher étudiant:<br>
|
||||||
<form method="get" id="form-chercheetud"
|
<form method="get" id="form-chercheetud"
|
||||||
action="{{ url_for('scolar.search_etud_in_dept', scodoc_dept=g.scodoc_dept) }}">
|
action="{{ url_for('scolar.search_etud_in_dept', scodoc_dept=g.scodoc_dept) }}">
|
||||||
<div>
|
<div>
|
||||||
@ -51,9 +48,8 @@
|
|||||||
<div class="etud-insidebar">
|
<div class="etud-insidebar">
|
||||||
{% if sco.etud %}
|
{% if sco.etud %}
|
||||||
<h2 id="insidebar-etud"><a href="{{url_for(
|
<h2 id="insidebar-etud"><a href="{{url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=sco.etud.id
|
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=sco.etud.id )}}" class="sidebar">
|
||||||
)}}" class="sidebar">
|
<span class="fontred">{{sco.etud.nomprenom}}</span></a>
|
||||||
<span class="fontred">{{sco.etud.civilite_str()}} {{sco.etud.nom_disp()}}</span></a>
|
|
||||||
</h2>
|
</h2>
|
||||||
<b>Absences</b>
|
<b>Absences</b>
|
||||||
{% if sco.etud_cur_sem %}
|
{% if sco.etud_cur_sem %}
|
||||||
|
@ -431,7 +431,7 @@ def SignaleAbsenceGrHebdo(
|
|||||||
modimpls_list = []
|
modimpls_list = []
|
||||||
ues = nt.get_ues_stat_dict()
|
ues = nt.get_ues_stat_dict()
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"])
|
modimpls_list += nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||||
|
|
||||||
menu_module = ""
|
menu_module = ""
|
||||||
for modimpl in modimpls_list:
|
for modimpl in modimpls_list:
|
||||||
@ -599,7 +599,7 @@ def SignaleAbsenceGrSemestre(
|
|||||||
modimpls_list = []
|
modimpls_list = []
|
||||||
ues = nt.get_ues_stat_dict()
|
ues = nt.get_ues_stat_dict()
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"])
|
modimpls_list += nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||||
|
|
||||||
menu_module = ""
|
menu_module = ""
|
||||||
for modimpl in modimpls_list:
|
for modimpl in modimpls_list:
|
||||||
|
@ -41,6 +41,7 @@ from flask_wtf import FlaskForm
|
|||||||
from flask_wtf.file import FileField, FileAllowed
|
from flask_wtf.file import FileField, FileAllowed
|
||||||
from wtforms import SubmitField
|
from wtforms import SubmitField
|
||||||
|
|
||||||
|
from app import log
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
scodoc,
|
scodoc,
|
||||||
scodoc7func,
|
scodoc7func,
|
||||||
@ -50,12 +51,12 @@ from app.decorators import (
|
|||||||
login_required,
|
login_required,
|
||||||
)
|
)
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
from app.models.etudiants import make_etud_args
|
||||||
|
|
||||||
from app.views import scolar_bp as bp
|
from app.views import scolar_bp as bp
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import (
|
||||||
@ -455,7 +456,7 @@ def etud_info(etudid=None, format="xml"):
|
|||||||
if not format in ("xml", "json"):
|
if not format in ("xml", "json"):
|
||||||
raise ScoValueError("format demandé non supporté par cette fonction.")
|
raise ScoValueError("format demandé non supporté par cette fonction.")
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
args = sco_etud.make_etud_args(etudid=etudid)
|
args = make_etud_args(etudid=etudid)
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
etuds = sco_etud.etudident_list(cnx, args)
|
etuds = sco_etud.etudident_list(cnx, args)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
|
2
pylintrc
2
pylintrc
@ -19,3 +19,5 @@ ignored-classes=Permission,
|
|||||||
# and thus existing member attributes cannot be deduced by static analysis). It
|
# and thus existing member attributes cannot be deduced by static analysis). It
|
||||||
# supports qualified module names, as well as Unix pattern matching.
|
# supports qualified module names, as well as Unix pattern matching.
|
||||||
ignored-modules=entreprises
|
ignored-modules=entreprises
|
||||||
|
|
||||||
|
good-names=d,e,f,i,j,k,t,u,v,x,y,z,H,F,ue
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.1.16"
|
SCOVERSION = "9.2.0a"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ et calcul moyennes modules
|
|||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from app.models.modules import Module
|
||||||
|
from app.models.moduleimpls import ModuleImpl
|
||||||
|
|
||||||
from tests.unit import setup
|
from tests.unit import setup
|
||||||
from app import db
|
from app import db
|
||||||
@ -135,70 +137,72 @@ def test_module_conformity(test_client):
|
|||||||
)
|
)
|
||||||
assert isinstance(modules_coefficients, pd.DataFrame)
|
assert isinstance(modules_coefficients, pd.DataFrame)
|
||||||
assert modules_coefficients.shape == (nb_ues, nb_mods)
|
assert modules_coefficients.shape == (nb_ues, nb_mods)
|
||||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(evaluation.moduleimpl_id)
|
evals_poids, ues = moy_mod.load_evaluations_poids(evaluation.moduleimpl_id)
|
||||||
assert isinstance(evals_poids, pd.DataFrame)
|
assert isinstance(evals_poids, pd.DataFrame)
|
||||||
assert len(ues) == nb_ues
|
assert len(ues) == nb_ues
|
||||||
assert all(evals_poids.dtypes == np.float64)
|
assert all(evals_poids.dtypes == np.float64)
|
||||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||||
assert not moy_mod.check_moduleimpl_conformity(
|
assert not moy_mod.moduleimpl_is_conforme(
|
||||||
evaluation.moduleimpl, evals_poids, modules_coefficients
|
evaluation.moduleimpl, evals_poids, modules_coefficients
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_module_moy_elem(test_client):
|
# En ScoDoc 9.2 test ne peut plus exister car compute_module_moy
|
||||||
"""Vérification calcul moyenne d'un module
|
# est maintenant incorporé dans la classe ModuleImplResultsAPC
|
||||||
(notes entrées dans un DataFrame sans passer par ScoDoc)
|
# def test_module_moy_elem(test_client):
|
||||||
"""
|
# """Vérification calcul moyenne d'un module
|
||||||
# Création de deux évaluations:
|
# (notes entrées dans un DataFrame sans passer par ScoDoc)
|
||||||
e1 = Evaluation(note_max=20.0, coefficient=1.0)
|
# """
|
||||||
e2 = Evaluation(note_max=20.0, coefficient=1.0)
|
# # Création de deux évaluations:
|
||||||
db.session.add(e1)
|
# e1 = Evaluation(note_max=20.0, coefficient=1.0)
|
||||||
db.session.add(e2)
|
# e2 = Evaluation(note_max=20.0, coefficient=1.0)
|
||||||
db.session.commit()
|
# db.session.add(e1)
|
||||||
# Repris du notebook CalculNotesBUT.ipynb
|
# db.session.add(e2)
|
||||||
data = [ # Les notes de chaque étudiant dans les 2 evals:
|
# db.session.flush()
|
||||||
{
|
# # Repris du notebook CalculNotesBUT.ipynb
|
||||||
e1.id: 11.0,
|
# data = [ # Les notes de chaque étudiant dans les 2 evals:
|
||||||
e2.id: 16.0,
|
# {
|
||||||
},
|
# e1.id: 11.0,
|
||||||
{
|
# e2.id: 16.0,
|
||||||
e1.id: None, # une absence
|
# },
|
||||||
e2.id: 17.0,
|
# {
|
||||||
},
|
# e1.id: None, # une absence
|
||||||
{
|
# e2.id: 17.0,
|
||||||
e1.id: 13.0,
|
# },
|
||||||
e2.id: NOTES_NEUTRALISE, # une abs EXC
|
# {
|
||||||
},
|
# e1.id: 13.0,
|
||||||
{
|
# e2.id: NOTES_NEUTRALISE, # une abs EXC
|
||||||
e1.id: 14.0,
|
# },
|
||||||
e2.id: 19.0,
|
# {
|
||||||
},
|
# e1.id: 14.0,
|
||||||
{
|
# e2.id: 19.0,
|
||||||
e1.id: NOTES_ATTENTE, # une ATT (traitée comme EXC)
|
# },
|
||||||
e2.id: None, # et une ABS
|
# {
|
||||||
},
|
# e1.id: NOTES_ATTENTE, # une ATT (traitée comme EXC)
|
||||||
]
|
# e2.id: None, # et une ABS
|
||||||
evals_notes_df = pd.DataFrame(
|
# },
|
||||||
data, index=["etud1", "etud2", "etud3", "etud4", "etud5"]
|
# ]
|
||||||
)
|
# evals_notes_df = pd.DataFrame(
|
||||||
# Poids des évaluations (1 ligne / évaluation)
|
# data, index=["etud1", "etud2", "etud3", "etud4", "etud5"]
|
||||||
data = [
|
# )
|
||||||
{"UE1": 1, "UE2": 0, "UE3": 0},
|
# # Poids des évaluations (1 ligne / évaluation)
|
||||||
{"UE1": 2, "UE2": 5, "UE3": 0},
|
# data = [
|
||||||
]
|
# {"UE1": 1, "UE2": 0, "UE3": 0},
|
||||||
evals_poids_df = pd.DataFrame(data, index=[e1.id, e2.id], dtype=float)
|
# {"UE1": 2, "UE2": 5, "UE3": 0},
|
||||||
evaluations = [e1, e2]
|
# ]
|
||||||
etuds_moy_module_df = moy_mod.compute_module_moy(
|
# evals_poids_df = pd.DataFrame(data, index=[e1.id, e2.id], dtype=float)
|
||||||
evals_notes_df.fillna(0.0), evals_poids_df, evaluations, [True, True]
|
# evaluations = [e1, e2]
|
||||||
)
|
# etuds_moy_module_df = moy_mod.compute_module_moy(
|
||||||
NAN = 666.0 # pour pouvoir comparer NaN et NaN (car NaN != NaN)
|
# evals_notes_df.fillna(0.0), evals_poids_df, evaluations, [True, True]
|
||||||
r = etuds_moy_module_df.fillna(NAN)
|
# )
|
||||||
assert tuple(r.loc["etud1"]) == (14 + 1 / 3, 16.0, NAN)
|
# NAN = 666.0 # pour pouvoir comparer NaN et NaN (car NaN != NaN)
|
||||||
assert tuple(r.loc["etud2"]) == (11 + 1 / 3, 17.0, NAN)
|
# r = etuds_moy_module_df.fillna(NAN)
|
||||||
assert tuple(r.loc["etud3"]) == (13, NAN, NAN)
|
# assert tuple(r.loc["etud1"]) == (14 + 1 / 3, 16.0, NAN)
|
||||||
assert tuple(r.loc["etud4"]) == (17 + 1 / 3, 19, NAN)
|
# assert tuple(r.loc["etud2"]) == (11 + 1 / 3, 17.0, NAN)
|
||||||
assert tuple(r.loc["etud5"]) == (0.0, 0.0, NAN)
|
# assert tuple(r.loc["etud3"]) == (13, NAN, NAN)
|
||||||
# note: les notes UE3 sont toutes NAN car les poids vers l'UE3 sont nuls
|
# assert tuple(r.loc["etud4"]) == (17 + 1 / 3, 19, NAN)
|
||||||
|
# assert tuple(r.loc["etud5"]) == (0.0, 0.0, NAN)
|
||||||
|
# # note: les notes UE3 sont toutes NAN car les poids vers l'UE3 sont nuls
|
||||||
|
|
||||||
|
|
||||||
def test_module_moy(test_client):
|
def test_module_moy(test_client):
|
||||||
@ -237,7 +241,7 @@ def test_module_moy(test_client):
|
|||||||
nb_evals = models.Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).count()
|
nb_evals = models.Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).count()
|
||||||
assert nb_evals == 2
|
assert nb_evals == 2
|
||||||
nb_ues = 3
|
nb_ues = 3
|
||||||
|
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
# --- Change les notes et recalcule les moyennes du module
|
# --- Change les notes et recalcule les moyennes du module
|
||||||
# (rappel: on a deux évaluations: evaluation1, evaluation2, et un seul étudiant)
|
# (rappel: on a deux évaluations: evaluation1, evaluation2, et un seul étudiant)
|
||||||
def change_notes(n1, n2):
|
def change_notes(n1, n2):
|
||||||
@ -245,17 +249,14 @@ def test_module_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)])
|
||||||
# Calcul de la moyenne du module
|
# Calcul de la moyenne du module
|
||||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(moduleimpl_id)
|
evals_poids, ues = moy_mod.load_evaluations_poids(moduleimpl_id)
|
||||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||||
evals_notes, evaluations, evaluations_completes = moy_mod.df_load_modimpl_notes(
|
|
||||||
moduleimpl_id
|
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
||||||
)
|
evals_notes = mod_results.evals_notes
|
||||||
assert evals_notes[evaluations[0].id].dtype == np.float64
|
assert evals_notes[evaluation1.id].dtype == np.float64
|
||||||
assert evaluation1.id == evaluations[0].id
|
|
||||||
assert evaluation2.id == evaluations[1].id
|
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||||
etuds_moy_module = moy_mod.compute_module_moy(
|
|
||||||
evals_notes, evals_poids, evaluations, evaluations_completes
|
|
||||||
)
|
|
||||||
return etuds_moy_module
|
return etuds_moy_module
|
||||||
|
|
||||||
# --- Notes ordinaires:
|
# --- Notes ordinaires:
|
||||||
|
@ -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)
|
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
|
||||||
@ -112,7 +112,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)
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user