Bonus sport multiplicatifs ou additifs sur bulletins DUT et BUT

This commit is contained in:
Emmanuel Viennet 2022-01-29 22:45:39 +01:00
parent 7e4459a15e
commit 23672bebde
8 changed files with 186 additions and 112 deletions

View File

@ -28,21 +28,29 @@ class BulletinBUT(ResultatsSemestreBUT):
"dict synthèse résultats dans l'UE pour les modules indiqués" "dict synthèse résultats dans l'UE pour les modules indiqués"
d = {} d = {}
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) if ue.type != UE_SPORT:
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 modimpl in modimpls: for modimpl in modimpls:
if self.modimpl_inscr_df[str(modimpl.id)][etud.id]: # si inscrit if self.modimpl_inscr_df[str(modimpl.id)][etud.id]: # si inscrit
coef = self.modimpl_coefs_df[modimpl.id][ue.id] if ue.type != UE_SPORT:
if coef > 0: coef = self.modimpl_coefs_df[modimpl.id][ue.id]
d[modimpl.module.code] = { if coef > 0:
"id": modimpl.id, d[modimpl.module.code] = {
"coef": coef, "id": modimpl.id,
"moyenne": fmt_note( "coef": coef,
etud_moy_module[ "moyenne": fmt_note(
self.modimpl_coefs_df.columns.get_loc(modimpl.id) etud_moy_module[
][ue_idx] self.modimpl_coefs_df.columns.get_loc(modimpl.id)
), ][ue_idx]
} ),
}
# else: # modules dans UE bonus sport
# d[modimpl.module.code] = {
# "id": modimpl.id,
# "coef": "",
# "moyenne": "?x?",
# }
return d return d
def etud_ue_results(self, etud, ue): def etud_ue_results(self, etud, ue):

View File

@ -38,7 +38,7 @@ class BonusSport:
notes moyennes aux modules (tous les étuds x tous les modimpls) notes moyennes aux modules (tous les étuds x tous les modimpls)
floats avec des NaN. floats avec des NaN.
En classique: sem_matrix, ndarray (etuds x modimpls) En classique: sem_matrix, ndarray (etuds x modimpls)
En APC: sem_cube, ndarray (etuds x modimpls x UEs) En APC: sem_cube, ndarray (etuds x modimpls x UEs non bonus)
- ues: les ues du semestre (incluant le bonus sport) - ues: les ues du semestre (incluant le bonus sport)
- modimpl_inscr_df: matrice d'inscription aux modules du semestre (etud x modimpl) - modimpl_inscr_df: matrice d'inscription aux modules du semestre (etud x modimpl)
- modimpl_coefs: les coefs des modules - modimpl_coefs: les coefs des modules
@ -50,10 +50,9 @@ class BonusSport:
etud_moy_gen et etud_moy_ue ne sont PAS modifiés (mais utilisés par certains bonus non additifs). etud_moy_gen et etud_moy_ue ne sont PAS modifiés (mais utilisés par certains bonus non additifs).
""" """
# Si vrai, en APC, si le bonus UE est None, reporte le bonus moy gen: # En classique, active un bonus sur les UEs: (dans ce cas bonus_moy_gen reste None)
apc_apply_bonus_mg_to_ues = True classic_use_bonus_ues = False
# Si True, reporte toujours le bonus moy gen sur les UE (même en formations classiques)
apply_bonus_mg_to_ues = False
# Attributs virtuels: # Attributs virtuels:
seuil_moy_gen = None seuil_moy_gen = None
proportion_point = None proportion_point = None
@ -77,7 +76,7 @@ class BonusSport:
self.etud_moy_ue = etud_moy_ue self.etud_moy_ue = etud_moy_ue
self.etuds_idx = modimpl_inscr_df.index # les étudiants inscrits au semestre self.etuds_idx = modimpl_inscr_df.index # les étudiants inscrits au semestre
self.bonus_ues: pd.DataFrame = None # virtual self.bonus_ues: pd.DataFrame = None # virtual
self.bonus_moy_gen: pd.Series = None # virtual self.bonus_moy_gen: pd.Series = None # virtual (pour formations non apc slt)
# Restreint aux modules standards des UE de type "sport": # Restreint aux modules standards des UE de type "sport":
modimpl_mask = np.array( modimpl_mask = np.array(
[ [
@ -94,13 +93,14 @@ class BonusSport:
"liste des modimpls sport" "liste des modimpls sport"
# Les moyennes des modules "sport": (une par UE en APC) # Les moyennes des modules "sport": (une par UE en APC)
# donc (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
sem_modimpl_moys_spo = sem_modimpl_moys[:, modimpl_mask] sem_modimpl_moys_spo = sem_modimpl_moys[:, modimpl_mask]
# Les inscriptions aux modules sport: # Les inscriptions aux modules sport:
modimpl_inscr_spo = modimpl_inscr_df.values[:, modimpl_mask] modimpl_inscr_spo = modimpl_inscr_df.values[:, modimpl_mask]
# Les coefficients des modules sport (en apc: nb_mod_sport x nb_ue) # Les coefficients des modules sport (en apc: nb_mod_sport x nb_ue) (toutes ues)
modimpl_coefs_spo = modimpl_coefs[modimpl_mask] modimpl_coefs_spo = modimpl_coefs[modimpl_mask]
# sem_modimpl_moys_spo est (nb_etuds, nb_mod_sport) # sem_modimpl_moys_spo est (nb_etuds, nb_mod_sport)
# ou (nb_etuds, nb_mod_sport, nb_ues) # ou (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
nb_etuds, nb_mod_sport = sem_modimpl_moys_spo.shape[:2] nb_etuds, nb_mod_sport = sem_modimpl_moys_spo.shape[:2]
nb_ues = len(ues) nb_ues = len(ues)
# Enlève les NaN du numérateur: # Enlève les NaN du numérateur:
@ -115,7 +115,7 @@ class BonusSport:
[modimpl_inscr_spo] * nb_ues_no_bonus, axis=2 [modimpl_inscr_spo] * nb_ues_no_bonus, axis=2
) )
# Ne prend pas en compte les notes des étudiants non inscrits au module: # Ne prend pas en compte les notes des étudiants non inscrits au module:
# Annule les notes: # Annule les notes: (nb_etuds, nb_mod_bonus, nb_ues_non_bonus)
sem_modimpl_moys_inscrits = np.where( sem_modimpl_moys_inscrits = np.where(
modimpl_inscr_spo_stacked, sem_modimpl_moys_no_nan, 0.0 modimpl_inscr_spo_stacked, sem_modimpl_moys_no_nan, 0.0
) )
@ -151,7 +151,7 @@ class BonusSport:
"""Calcul des bonus: méthode virtuelle à écraser. """Calcul des bonus: méthode virtuelle à écraser.
Arguments: Arguments:
- sem_modimpl_moys_inscrits: - sem_modimpl_moys_inscrits:
ndarray (nb_etuds, mod_sport) ou en APC (nb_etuds, mods_sport, nb_ue) ndarray (nb_etuds, mod_sport) ou en APC (nb_etuds, mods_sport, nb_ue_non_bonus)
les notes aux modules sports auxquel l'étudiant est inscrit, 0 sinon. Pas de nans. les notes aux modules sports auxquel l'étudiant est inscrit, 0 sinon. Pas de nans.
- modimpl_coefs_etuds_no_nan: - modimpl_coefs_etuds_no_nan:
les coefficients: float ndarray les coefficients: float ndarray
@ -164,24 +164,16 @@ class BonusSport:
"""Les bonus à appliquer aux UE """Les bonus à appliquer aux UE
Résultat: DataFrame de float, index etudid, columns: ue.id Résultat: DataFrame de float, index etudid, columns: ue.id
""" """
if self.bonus_ues is None and ( if self.classic_use_bonus_ues or self.formsemestre.formation.is_apc():
(self.apc_apply_bonus_mg_to_ues and self.formsemestre.formation.is_apc()) return self.bonus_ues
or self.apply_bonus_mg_to_ues return None
):
# reporte uniformément le bonus moyenne générale sur les UEs
# (assure la compatibilité de la plupart des anciens bonus avec le BUT)
# ues = self.formsemestre.query_ues(with_sport=False)
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
bonus_moy_gen = self.get_bonus_moy_gen()
bonus_ues = np.stack([bonus_moy_gen.values] * len(ues_idx), axis=1)
return pd.DataFrame(bonus_ues, index=self.etuds_idx, columns=ues_idx)
return self.bonus_ues
def get_bonus_moy_gen(self): def get_bonus_moy_gen(self):
"""Le bonus à appliquer à la moyenne générale. """Le bonus à appliquer à la moyenne générale.
Résultat: Series de float, index etudid Résultat: Series de float, index etudid
""" """
if self.formsemestre.formation.is_apc():
return None # garde-fou
return self.bonus_moy_gen return self.bonus_moy_gen
@ -200,8 +192,12 @@ class BonusSportAdditif(BonusSport):
proportion_point = 0.05 # multiplie les points au dessus du seuil proportion_point = 0.05 # multiplie les points au dessus du seuil
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus""" """calcul du bonus
bonus_moy_gen_arr = np.sum( sem_modimpl_moys_inscrits: les notes de sport
En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
modimpl_coefs_etuds_no_nan:
"""
bonus_moy_arr = np.sum(
np.where( np.where(
sem_modimpl_moys_inscrits > self.seuil_moy_gen, sem_modimpl_moys_inscrits > self.seuil_moy_gen,
(sem_modimpl_moys_inscrits - self.seuil_moy_gen) (sem_modimpl_moys_inscrits - self.seuil_moy_gen)
@ -210,18 +206,28 @@ class BonusSportAdditif(BonusSport):
), ),
axis=1, axis=1,
) )
# en APC, applati la moyenne gen. XXX pourrait être fait en amont
if len(bonus_moy_gen_arr.shape) > 1:
bonus_moy_gen_arr = bonus_moy_gen_arr.sum(axis=1)
# Bonus moyenne générale, et 0 sur les UE
self.bonus_moy_gen = pd.Series(
bonus_moy_gen_arr, index=self.etuds_idx, dtype=float
)
if self.bonus_max is not None: if self.bonus_max is not None:
# Seuil: bonus (sur moy. gen.) limité à bonus_max points # Seuil: bonus limité à bonus_max points (et >= 0)
self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_max) bonus_moy_arr = np.clip(
bonus_moy_arr, 0.0, self.bonus_max, out=bonus_moy_arr
)
# Laisse bonus_ues à None, en APC le bonus moy. gen. sera réparti sur les UEs. # en APC, bonus_moy_arr est (nb_etuds, nb_ues_non_bonus)
if self.formsemestre.formation.is_apc() or self.classic_use_bonus_ues:
# Bonus sur les UE et None sur moyenne générale
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
self.bonus_ues = pd.DataFrame(
bonus_moy_arr, index=self.etuds_idx, columns=ues_idx, dtype=float
)
else:
# Bonus sur la moyenne générale seulement
self.bonus_moy_gen = pd.Series(
bonus_moy_arr, index=self.etuds_idx, dtype=float
)
# if len(bonus_moy_arr.shape) > 1:
# bonus_moy_arr = bonus_moy_arr.sum(axis=1)
# Laisse bonus_moy_gen à None, en APC le bonus moy. gen. sera réparti sur les UEs.
class BonusSportMultiplicatif(BonusSport): class BonusSportMultiplicatif(BonusSport):
@ -229,6 +235,8 @@ class BonusSportMultiplicatif(BonusSport):
seuil_moy_gen = 10.0 # seuls les points au dessus du seuil sont comptés seuil_moy_gen = 10.0 # seuls les points au dessus du seuil sont comptés
amplitude = 0.005 # multiplie les points au dessus du seuil amplitude = 0.005 # multiplie les points au dessus du seuil
# En classique, les bonus multiplicatifs agissent par défaut sur les UE:
classic_use_bonus_ues = True
# C'est un bonus "multiplicatif": on l'exprime en additif, # C'est un bonus "multiplicatif": on l'exprime en additif,
# sur chaque moyenne d'UE m_0 # sur chaque moyenne d'UE m_0
@ -243,11 +251,12 @@ class BonusSportMultiplicatif(BonusSport):
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1 sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1) ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
notes = np.nan_to_num(notes, copy=False) notes = np.nan_to_num(notes, copy=False)
factor = (notes - self.seuil_moy_gen) * self.amplitude # 5% si note=20 factor = (notes - self.seuil_moy_gen) * self.amplitude # 5% si note=20
factor[factor <= 0] = 0.0 # note < seuil_moy_gen, pas de bonus factor[factor <= 0] = 0.0 # note < seuil_moy_gen, pas de bonus
# Ne s'applique qu'aux moyennes d'UE # Ne s'applique qu'aux moyennes d'UE
if len(factor.shape) == 1: # classic
factor = factor[:, np.newaxis]
bonus = self.etud_moy_ue * factor bonus = self.etud_moy_ue * factor
if self.bonus_max is not None: if self.bonus_max is not None:
# Seuil: bonus limité à bonus_max points # Seuil: bonus limité à bonus_max points
@ -255,9 +264,8 @@ class BonusSportMultiplicatif(BonusSport):
self.bonus_ues = bonus # DataFrame self.bonus_ues = bonus # DataFrame
if not self.formsemestre.formation.is_apc(): # Les bonus multiplicatifs ne s'appliquent pas à la moyenne générale
# s'applique à la moyenne générale self.bonus_moy_gen = None
self.bonus_moy_gen = bonus
class BonusDirect(BonusSportAdditif): class BonusDirect(BonusSportAdditif):
@ -323,6 +331,7 @@ class BonusBordeaux1(BonusSportMultiplicatif):
name = "bonus_iutBordeaux1" name = "bonus_iutBordeaux1"
displayed_name = "IUT de Bordeaux 1" displayed_name = "IUT de Bordeaux 1"
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
seuil_moy_gen = 10.0 seuil_moy_gen = 10.0
amplitude = 0.005 amplitude = 0.005
@ -576,17 +585,15 @@ class BonusVilleAvray(BonusSport):
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus""" """calcul du bonus"""
# Calcule moyenne pondérée des notes de sport: # Calcule moyenne pondérée des notes de sport:
bonus_moy_gen_arr = np.sum( bonus_moy_arr = np.sum(
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1 sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1) ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
bonus_moy_gen_arr[bonus_moy_gen_arr >= 10.0] = 0.1 bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.1
bonus_moy_gen_arr[bonus_moy_gen_arr >= 12.0] = 0.2 bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2
bonus_moy_gen_arr[bonus_moy_gen_arr >= 16.0] = 0.3 bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.3
# Bonus moyenne générale, et 0 sur les UE # Bonus moyenne générale, et 0 sur les UE
self.bonus_moy_gen = pd.Series( self.bonus_moy_gen = pd.Series(bonus_moy_arr, index=self.etuds_idx, dtype=float)
bonus_moy_gen_arr, index=self.etuds_idx, dtype=float
)
if self.bonus_max is not None: if self.bonus_max is not None:
# Seuil: bonus (sur moy. gen.) limité à bonus_max points # Seuil: bonus (sur moy. gen.) limité à bonus_max points
self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_max) self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_max)

View File

@ -40,6 +40,7 @@ import pandas as pd
from app import db from app import db
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
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
@ -269,7 +270,8 @@ def load_evaluations_poids(moduleimpl_id: int) -> 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
rows = evaluations, columns = UE, value = poids (float). rows = evaluations, columns = UE, value = poids (float).
Les valeurs manquantes (évaluations sans coef vers des UE) sont Les valeurs manquantes (évaluations sans coef vers des UE) sont
remplies: 1 si le coef de ce module dans l'UE est non nul, zéro sinon. remplies: 1 si le coef de ce module dans l'UE est non nul, zéro sinon
(sauf pour module bonus, defaut à 1)
Résultat: (evals_poids, liste de UEs du semestre sauf le sport) Résultat: (evals_poids, liste de UEs du semestre sauf le sport)
""" """
modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id) modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
@ -287,11 +289,13 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre... pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
# Initialise poids non enregistrés: # Initialise poids non enregistrés:
default_poids = 1.0 if modimpl.module.ue.type == UE_SPORT else 0.0
if np.isnan(evals_poids.values.flat).any(): if np.isnan(evals_poids.values.flat).any():
ue_coefs = modimpl.module.get_ue_coef_dict() ue_coefs = modimpl.module.get_ue_coef_dict()
for ue in ues: for ue in ues:
evals_poids[ue.id][evals_poids[ue.id].isna()] = ( evals_poids[ue.id][evals_poids[ue.id].isna()] = (
1 if ue_coefs.get(ue.id, 0.0) > 0 else 0 1 if ue_coefs.get(ue.id, default_poids) > 0 else 0
) )
return evals_poids, ues return evals_poids, ues

View File

@ -36,6 +36,7 @@ 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.models.formsemestre import FormSemestre
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
@ -44,10 +45,10 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
En APC, ces coefs lient les modules à chaque UE. En APC, ces coefs lient les modules à chaque UE.
Résultat: (module_coefs_df, ues, modules) Résultat: (module_coefs_df, ues_no_bonus, modules)
DataFrame rows = UEs, columns = modules, value = coef. DataFrame rows = UEs, columns = modules, value = coef.
Considère toutes les UE (sauf sport) et modules du semestre. Considère toutes les UE sauf bonus et tous les modules du semestre.
Les coefs non définis (pas en base) sont mis à zéro. Les coefs non définis (pas en base) sont mis à zéro.
Si semestre_idx None, prend toutes les UE de la formation. Si semestre_idx None, prend toutes les UE de la formation.
@ -92,7 +93,17 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
module_coefs_df[mod_coef.module_id][mod_coef.ue_id] = mod_coef.coef module_coefs_df[mod_coef.module_id][mod_coef.ue_id] = mod_coef.coef
# silently ignore coefs associated to other modules (ie when module_type is changed) # silently ignore coefs associated to other modules (ie when module_type is changed)
module_coefs_df.fillna(value=0, inplace=True) # Initialisation des poids non fixés:
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
# sur toutes les UE)
default_poids = {
mod.id: 1.0
if (mod.module_type == ModuleType.STANDARD) and (mod.ue.type == UE_SPORT)
else 0.0
for mod in modules
}
module_coefs_df.fillna(value=default_poids, inplace=True)
return module_coefs_df, ues, modules return module_coefs_df, ues, modules
@ -104,9 +115,9 @@ def df_load_modimpl_coefs(
Comme df_load_module_coefs mais prend seulement les UE Comme df_load_module_coefs mais prend seulement les UE
et modules du formsemestre. et modules du formsemestre.
Si ues et modimpls sont None, prend tous ceux du formsemestre. Si ues et modimpls sont None, prend tous ceux du formsemestre (sauf ue bonus).
Résultat: (module_coefs_df, ues, modules) Résultat: (module_coefs_df, ues, modules)
DataFrame rows = UEs (avec bonus), columns = modimpl, value = coef. DataFrame rows = UEs (sans bonus), columns = modimpl, value = coef.
""" """
if ues is None: if ues is None:
ues = formsemestre.query_ues().all() ues = formsemestre.query_ues().all()
@ -124,7 +135,19 @@ def df_load_modimpl_coefs(
for mod_coef in mod_coefs: for mod_coef in mod_coefs:
modimpl_coefs_df[mod2impl[mod_coef.module_id]][mod_coef.ue_id] = mod_coef.coef modimpl_coefs_df[mod2impl[mod_coef.module_id]][mod_coef.ue_id] = mod_coef.coef
modimpl_coefs_df.fillna(value=0, inplace=True)
# Initialisation des poids non fixés:
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
# sur toutes les UE)
default_poids = {
modimpl.id: 1.0
if (modimpl.module.module_type == ModuleType.STANDARD)
and (modimpl.module.ue.type == UE_SPORT)
else 0.0
for modimpl in formsemestre.modimpls_sorted
}
modimpl_coefs_df.fillna(value=default_poids, inplace=True)
return modimpl_coefs_df, ues, modimpls return modimpl_coefs_df, ues, modimpls

View File

@ -40,22 +40,22 @@ class ResultatsSemestreBUT(NotesTableCompat):
) = 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(
self.formsemestre, ues=self.ues, modimpls=self.formsemestre.modimpls_sorted self.formsemestre, modimpls=self.formsemestre.modimpls_sorted
) )
# l'idx de la colonne du mod modimpl.id est # l'idx de la colonne du mod modimpl.id est
# modimpl_coefs_df.columns.get_loc(modimpl.id) # modimpl_coefs_df.columns.get_loc(modimpl.id)
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id) # idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
# Elimine les coefs des UE bonus sports # Elimine les coefs des UE bonus sports XXX inutile car df_load_modimpl_coefs sans bonus
no_bonus = [ue.type != UE_SPORT for ue in self.ues] # no_bonus = [ue.type != UE_SPORT for ue in self.ues]
modimpl_coefs_no_bonus_df = self.modimpl_coefs_df[no_bonus] # modimpl_coefs_no_bonus_df = self.modimpl_coefs_df[no_bonus]
self.etud_moy_ue = moy_ue.compute_ue_moys_apc( self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
self.sem_cube, self.sem_cube,
self.etuds, self.etuds,
self.formsemestre.modimpls_sorted, self.formsemestre.modimpls_sorted,
self.ues, self.ues,
self.modimpl_inscr_df, self.modimpl_inscr_df,
modimpl_coefs_no_bonus_df, self.modimpl_coefs_df,
) )
# Les coefficients d'UE ne sont pas utilisés en APC # Les coefficients d'UE ne sont pas utilisés en APC
self.etud_coef_ue_df = pd.DataFrame( self.etud_coef_ue_df = pd.DataFrame(
@ -63,25 +63,33 @@ class ResultatsSemestreBUT(NotesTableCompat):
) )
# --- Bonus Sport & Culture # --- Bonus Sport & Culture
bonus_class = ScoDocSiteConfig.get_bonus_sport_class() modimpl_sport = [
if bonus_class is not None: modimpl
bonus: BonusSport = bonus_class( for modimpl in self.formsemestre.modimpls_sorted
self.formsemestre, if modimpl.module.ue.type == UE_SPORT
self.sem_cube, ]
self.ues, if len(modimpl_sport) > 0:
self.modimpl_inscr_df, bonus_class = ScoDocSiteConfig.get_bonus_sport_class()
self.modimpl_coefs_df.transpose(), if bonus_class is not None:
self.etud_moy_gen, bonus: BonusSport = bonus_class(
self.etud_moy_ue, self.formsemestre,
) self.sem_cube,
self.bonus_ues = bonus.get_bonus_ues() self.ues,
if self.bonus_ues is not None: self.modimpl_inscr_df,
self.etud_moy_ue += self.bonus_ues # somme les dataframes self.modimpl_coefs_df.transpose(),
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True) self.etud_moy_gen,
self.etud_moy_ue,
)
self.bonus_ues = bonus.get_bonus_ues()
if self.bonus_ues is not None:
self.etud_moy_ue += self.bonus_ues # somme les dataframes
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
# Moyenne générale indicative: # Moyenne générale indicative:
# (note: le bonus sport a déjà été appliqué aux moyenens d'UE, et impacte
# donc la moyenne indicative)
self.etud_moy_gen = moy_sem.compute_sem_moys_apc( self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
self.etud_moy_ue, modimpl_coefs_no_bonus_df self.etud_moy_ue, self.modimpl_coefs_df
) )
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen) self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)

View File

@ -219,18 +219,18 @@ class NotesTableCompat(ResultatsSemestre):
ues.append(d) ues.append(d)
return ues return ues
def get_modimpls_dict(self, ue_id=None): def get_modimpls_dict(self, ue_id=None) -> list[dict]:
"""Liste des modules pour une UE (ou toutes si ue_id==None), """Liste des modules pour une UE (ou toutes si ue_id==None),
triés par numéros (selon le type de formation) triés par numéros (selon le type de formation)
""" """
if ue_id is None: modimpls_dict = []
return [m.to_dict() for m in self.formsemestre.modimpls_sorted] for modimpl in self.formsemestre.modimpls_sorted:
else: if ue_id == None or modimpl.module.ue.id == ue_id:
return [ d = modimpl.to_dict()
m.to_dict() # compat ScoDoc < 9.2: ajoute matières
for m in self.formsemestre.modimpls_sorted d["mat"] = modimpl.module.matiere.to_dict()
if m.module.ue.id == ue_id modimpls_dict.append(d)
] return modimpls_dict
def get_etud_decision_sem(self, etudid: int) -> dict: def get_etud_decision_sem(self, etudid: int) -> dict:
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu. """Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
@ -259,13 +259,10 @@ class NotesTableCompat(ResultatsSemestre):
return "" return ""
return ins.etat return ins.etat
def get_etud_moy_gen(self, etudid): # -> float | str def get_etud_mat_moy(self, matiere_id, etudid):
"""Moyenne générale de cet etudiant dans ce semestre. """moyenne d'un étudiant dans une matière (ou NA si pas de notes)"""
Prend(ra) en compte les UE capitalisées. (TODO) XXX # non supporté en 9.2
Si apc, moyenne indicative. return "na"
Si pas de notes: 'NA'
"""
return self.etud_moy_gen[etudid]
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float: def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
"""La moyenne de l'étudiant dans le moduleimpl """La moyenne de l'étudiant dans le moduleimpl
@ -274,6 +271,14 @@ class NotesTableCompat(ResultatsSemestre):
""" """
raise NotImplementedError() # virtual method raise NotImplementedError() # virtual method
def get_etud_moy_gen(self, etudid): # -> float | str
"""Moyenne générale de cet etudiant dans ce semestre.
Prend(ra) en compte les UE capitalisées. (TODO) XXX
Si apc, moyenne indicative.
Si pas de notes: 'NA'
"""
return self.etud_moy_gen[etudid]
def get_etud_ue_status(self, etudid: int, ue_id: int): def get_etud_ue_status(self, etudid: int, ue_id: int):
coef_ue = self.etud_coef_ue_df[ue_id][etudid] coef_ue = self.etud_coef_ue_df[ue_id][etudid]
return { return {

View File

@ -161,3 +161,16 @@ class Matiere(db.Model):
numero = db.Column(db.Integer) # ordre de présentation numero = db.Column(db.Integer) # ordre de présentation
modules = db.relationship("Module", lazy="dynamic", backref="matiere") modules = db.relationship("Module", lazy="dynamic", backref="matiere")
def __repr__(self):
return f"""<{self.__class__.__name__}(id={self.id}, ue_id={
self.ue_id}, titre='{self.titre}')>"""
def to_dict(self):
"""as a dict, with the same conversions as in ScoDoc7"""
e = dict(self.__dict__)
e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators
e["ue_id"] = self.id
e["numero"] = e["numero"] if e["numero"] else 0
return e

View File

@ -310,7 +310,10 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
else: else:
x = "" x = ""
if isinstance(x, str): if isinstance(x, str):
u["cur_moy_ue_txt"] = "pas de bonus" if nt.bonus_ues is None:
u["cur_moy_ue_txt"] = "pas de bonus"
else:
u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs"
else: else:
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x u["cur_moy_ue_txt"] = "bonus de %.3g points" % x
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"]) u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
@ -380,7 +383,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
) )
else: else:
if prefs["bul_show_ue_rangs"] and ue["type"] != sco_codes_parcours.UE_SPORT: if prefs["bul_show_ue_rangs"] and ue["type"] != sco_codes_parcours.UE_SPORT:
if ue_attente: # nt.get_moduleimpls_attente(): if ue_attente or nt.ue_rangs[ue["ue_id"]][0] is None:
u["ue_descr_txt"] = "%s/%s" % ( u["ue_descr_txt"] = "%s/%s" % (
scu.RANG_ATTENTE_STR, scu.RANG_ATTENTE_STR,
nt.ue_rangs[ue["ue_id"]][1], nt.ue_rangs[ue["ue_id"]][1],
@ -398,8 +401,8 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
I["ues"].append(u) # ne montre pas les UE si non inscrit I["ues"].append(u) # ne montre pas les UE si non inscrit
# Accès par matieres # Accès par matieres
# voir si on supporte encore cela en #sco92 XXX # En #sco92, pas d'information
# I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid)) I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
# #
C = make_context_dict(I["sem"], I["etud"]) C = make_context_dict(I["sem"], I["etud"])
@ -616,12 +619,15 @@ def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
# Classement # Classement
if bul_show_mod_rangs and mod["mod_moy_txt"] != "-" and not is_malus: if bul_show_mod_rangs and mod["mod_moy_txt"] != "-" and not is_malus:
rg = nt.mod_rangs[modimpl["moduleimpl_id"]] rg = nt.mod_rangs[modimpl["moduleimpl_id"]]
if mod_attente: # nt.get_moduleimpls_attente(): if rg[0] is None:
mod["mod_rang"] = scu.RANG_ATTENTE_STR mod["mod_rang_txt"] = ""
else: else:
mod["mod_rang"] = rg[0][etudid] if mod_attente: # nt.get_moduleimpls_attente():
mod["mod_eff"] = rg[1] # effectif dans ce module mod["mod_rang"] = scu.RANG_ATTENTE_STR
mod["mod_rang_txt"] = "%s/%s" % (mod["mod_rang"], mod["mod_eff"]) else:
mod["mod_rang"] = rg[0][etudid]
mod["mod_eff"] = rg[1] # effectif dans ce module
mod["mod_rang_txt"] = "%s/%s" % (mod["mod_rang"], mod["mod_eff"])
else: else:
mod["mod_rang_txt"] = "" mod["mod_rang_txt"] = ""
if mod_attente: if mod_attente: