From 23672bebded9484518e2ef60fd47d6b4a2f56676 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 29 Jan 2022 22:45:39 +0100 Subject: [PATCH] Bonus sport multiplicatifs ou additifs sur bulletins DUT et BUT --- app/but/bulletin_but.py | 32 +++++++----- app/comp/bonus_spo.py | 99 ++++++++++++++++++++----------------- app/comp/moy_mod.py | 8 ++- app/comp/moy_ue.py | 35 ++++++++++--- app/comp/res_but.py | 50 +++++++++++-------- app/comp/res_common.py | 37 ++++++++------ app/models/formations.py | 13 +++++ app/scodoc/sco_bulletins.py | 24 +++++---- 8 files changed, 186 insertions(+), 112 deletions(-) diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 11eb7e65e..b9e334786 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -28,21 +28,29 @@ class BulletinBUT(ResultatsSemestreBUT): "dict synthèse résultats dans l'UE pour les modules indiqués" d = {} etud_idx = self.etud_index[etud.id] - ue_idx = self.modimpl_coefs_df.index.get_loc(ue.id) + 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 for modimpl in modimpls: if self.modimpl_inscr_df[str(modimpl.id)][etud.id]: # si inscrit - coef = self.modimpl_coefs_df[modimpl.id][ue.id] - if coef > 0: - d[modimpl.module.code] = { - "id": modimpl.id, - "coef": coef, - "moyenne": fmt_note( - etud_moy_module[ - self.modimpl_coefs_df.columns.get_loc(modimpl.id) - ][ue_idx] - ), - } + if ue.type != UE_SPORT: + coef = self.modimpl_coefs_df[modimpl.id][ue.id] + if coef > 0: + d[modimpl.module.code] = { + "id": modimpl.id, + "coef": coef, + "moyenne": fmt_note( + etud_moy_module[ + 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 def etud_ue_results(self, etud, ue): diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index 9be542c58..e9f615cf5 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -38,7 +38,7 @@ class BonusSport: notes moyennes aux modules (tous les étuds x tous les modimpls) floats avec des NaN. 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) - modimpl_inscr_df: matrice d'inscription aux modules du semestre (etud x modimpl) - 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). """ - # Si vrai, en APC, si le bonus UE est None, reporte le bonus moy gen: - apc_apply_bonus_mg_to_ues = True - # Si True, reporte toujours le bonus moy gen sur les UE (même en formations classiques) - apply_bonus_mg_to_ues = False + # En classique, active un bonus sur les UEs: (dans ce cas bonus_moy_gen reste None) + classic_use_bonus_ues = False + # Attributs virtuels: seuil_moy_gen = None proportion_point = None @@ -77,7 +76,7 @@ class BonusSport: self.etud_moy_ue = etud_moy_ue self.etuds_idx = modimpl_inscr_df.index # les étudiants inscrits au semestre 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": modimpl_mask = np.array( [ @@ -94,13 +93,14 @@ class BonusSport: "liste des modimpls sport" # 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] # Les inscriptions aux modules sport: 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] # 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_ues = len(ues) # Enlève les NaN du numérateur: @@ -115,7 +115,7 @@ class BonusSport: [modimpl_inscr_spo] * nb_ues_no_bonus, axis=2 ) # 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( modimpl_inscr_spo_stacked, sem_modimpl_moys_no_nan, 0.0 ) @@ -151,7 +151,7 @@ class BonusSport: """Calcul des bonus: méthode virtuelle à écraser. Arguments: - 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. - modimpl_coefs_etuds_no_nan: les coefficients: float ndarray @@ -164,24 +164,16 @@ class BonusSport: """Les bonus à appliquer aux UE Résultat: DataFrame de float, index etudid, columns: ue.id """ - if self.bonus_ues is None and ( - (self.apc_apply_bonus_mg_to_ues and self.formsemestre.formation.is_apc()) - or self.apply_bonus_mg_to_ues - ): - # 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 + if self.classic_use_bonus_ues or self.formsemestre.formation.is_apc(): + return self.bonus_ues + return None def get_bonus_moy_gen(self): """Le bonus à appliquer à la moyenne générale. Résultat: Series de float, index etudid """ + if self.formsemestre.formation.is_apc(): + return None # garde-fou return self.bonus_moy_gen @@ -200,8 +192,12 @@ class BonusSportAdditif(BonusSport): proportion_point = 0.05 # multiplie les points au dessus du seuil def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): - """calcul du bonus""" - bonus_moy_gen_arr = np.sum( + """calcul du bonus + 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( 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, ) - # 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: - # Seuil: bonus (sur moy. gen.) limité à bonus_max points - self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_max) + # Seuil: bonus limité à bonus_max points (et >= 0) + 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): @@ -229,6 +235,8 @@ class BonusSportMultiplicatif(BonusSport): 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 + # 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, # 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 ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1) notes = np.nan_to_num(notes, copy=False) - factor = (notes - self.seuil_moy_gen) * self.amplitude # 5% si note=20 factor[factor <= 0] = 0.0 # note < seuil_moy_gen, pas de bonus # Ne s'applique qu'aux moyennes d'UE + if len(factor.shape) == 1: # classic + factor = factor[:, np.newaxis] bonus = self.etud_moy_ue * factor if self.bonus_max is not None: # Seuil: bonus limité à bonus_max points @@ -255,9 +264,8 @@ class BonusSportMultiplicatif(BonusSport): self.bonus_ues = bonus # DataFrame - if not self.formsemestre.formation.is_apc(): - # s'applique à la moyenne générale - self.bonus_moy_gen = bonus + # Les bonus multiplicatifs ne s'appliquent pas à la moyenne générale + self.bonus_moy_gen = None class BonusDirect(BonusSportAdditif): @@ -323,6 +331,7 @@ class BonusBordeaux1(BonusSportMultiplicatif): name = "bonus_iutBordeaux1" displayed_name = "IUT de Bordeaux 1" + classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP seuil_moy_gen = 10.0 amplitude = 0.005 @@ -576,17 +585,15 @@ class BonusVilleAvray(BonusSport): def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): """calcul du bonus""" # 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 ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1) - bonus_moy_gen_arr[bonus_moy_gen_arr >= 10.0] = 0.1 - bonus_moy_gen_arr[bonus_moy_gen_arr >= 12.0] = 0.2 - bonus_moy_gen_arr[bonus_moy_gen_arr >= 16.0] = 0.3 + bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.1 + bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2 + bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.3 # 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 - ) + self.bonus_moy_gen = pd.Series(bonus_moy_arr, index=self.etuds_idx, dtype=float) if self.bonus_max is not None: # Seuil: bonus (sur moy. gen.) limité à bonus_max points self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_max) diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index 1d8ff453e..1a64809fa 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -40,6 +40,7 @@ import pandas as pd from app import db from app.models import ModuleImpl, Evaluation, EvaluationUEPoids from app.scodoc import sco_utils as scu +from app.scodoc.sco_codes_parcours import UE_SPORT 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 rows = evaluations, columns = UE, value = poids (float). 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) """ 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... # 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(): ue_coefs = modimpl.module.get_ue_coef_dict() for ue in ues: 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 diff --git a/app/comp/moy_ue.py b/app/comp/moy_ue.py index cee1b8881..289822ff2 100644 --- a/app/comp/moy_ue.py +++ b/app/comp/moy_ue.py @@ -36,6 +36,7 @@ from app.models import UniteEns, Module, ModuleImpl, ModuleUECoef from app.comp import moy_mod from app.models.formsemestre import FormSemestre from app.scodoc import sco_codes_parcours +from app.scodoc.sco_codes_parcours import UE_SPORT 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. - Résultat: (module_coefs_df, ues, modules) + Résultat: (module_coefs_df, ues_no_bonus, modules) 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. 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 # 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 @@ -104,9 +115,9 @@ def df_load_modimpl_coefs( Comme df_load_module_coefs mais prend seulement les UE et modules du formsemestre. - Si ues et modimpls sont None, prend tous ceux du formsemestre. + Si ues et modimpls sont None, prend tous ceux du formsemestre (sauf ue bonus). 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: ues = formsemestre.query_ues().all() @@ -124,7 +135,19 @@ def df_load_modimpl_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.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 diff --git a/app/comp/res_but.py b/app/comp/res_but.py index e7c6d4c45..266cda97c 100644 --- a/app/comp/res_but.py +++ b/app/comp/res_but.py @@ -40,22 +40,22 @@ class ResultatsSemestreBUT(NotesTableCompat): ) = moy_ue.notes_sem_load_cube(self.formsemestre) self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre) self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs( - self.formsemestre, ues=self.ues, modimpls=self.formsemestre.modimpls_sorted + self.formsemestre, modimpls=self.formsemestre.modimpls_sorted ) # l'idx de la colonne du mod modimpl.id est # modimpl_coefs_df.columns.get_loc(modimpl.id) # idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id) - # Elimine les coefs des UE bonus sports - no_bonus = [ue.type != UE_SPORT for ue in self.ues] - modimpl_coefs_no_bonus_df = self.modimpl_coefs_df[no_bonus] + # 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] + # modimpl_coefs_no_bonus_df = self.modimpl_coefs_df[no_bonus] self.etud_moy_ue = moy_ue.compute_ue_moys_apc( self.sem_cube, self.etuds, self.formsemestre.modimpls_sorted, self.ues, self.modimpl_inscr_df, - modimpl_coefs_no_bonus_df, + self.modimpl_coefs_df, ) # Les coefficients d'UE ne sont pas utilisés en APC self.etud_coef_ue_df = pd.DataFrame( @@ -63,25 +63,33 @@ class ResultatsSemestreBUT(NotesTableCompat): ) # --- Bonus Sport & Culture - bonus_class = ScoDocSiteConfig.get_bonus_sport_class() - if bonus_class is not None: - bonus: BonusSport = bonus_class( - self.formsemestre, - self.sem_cube, - self.ues, - self.modimpl_inscr_df, - self.modimpl_coefs_df.transpose(), - 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) + modimpl_sport = [ + modimpl + for modimpl in self.formsemestre.modimpls_sorted + if modimpl.module.ue.type == UE_SPORT + ] + if len(modimpl_sport) > 0: + bonus_class = ScoDocSiteConfig.get_bonus_sport_class() + if bonus_class is not None: + bonus: BonusSport = bonus_class( + self.formsemestre, + self.sem_cube, + self.ues, + self.modimpl_inscr_df, + self.modimpl_coefs_df.transpose(), + 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: + # (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_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) diff --git a/app/comp/res_common.py b/app/comp/res_common.py index e2e5ab16a..a90ce1c12 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -219,18 +219,18 @@ class NotesTableCompat(ResultatsSemestre): ues.append(d) 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), triés par numéros (selon le type de formation) """ - if ue_id is None: - return [m.to_dict() for m in self.formsemestre.modimpls_sorted] - else: - return [ - m.to_dict() - for m in self.formsemestre.modimpls_sorted - if m.module.ue.id == ue_id - ] + modimpls_dict = [] + for modimpl in self.formsemestre.modimpls_sorted: + if ue_id == None or modimpl.module.ue.id == ue_id: + d = modimpl.to_dict() + # compat ScoDoc < 9.2: ajoute matières + d["mat"] = modimpl.module.matiere.to_dict() + modimpls_dict.append(d) + return modimpls_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. @@ -259,13 +259,10 @@ class NotesTableCompat(ResultatsSemestre): return "" return ins.etat - 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_mat_moy(self, matiere_id, etudid): + """moyenne d'un étudiant dans une matière (ou NA si pas de notes)""" + # non supporté en 9.2 + return "na" def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float: """La moyenne de l'étudiant dans le moduleimpl @@ -274,6 +271,14 @@ class NotesTableCompat(ResultatsSemestre): """ 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): coef_ue = self.etud_coef_ue_df[ue_id][etudid] return { diff --git a/app/models/formations.py b/app/models/formations.py index b69d566a6..edd57097d 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -161,3 +161,16 @@ class Matiere(db.Model): numero = db.Column(db.Integer) # ordre de présentation 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 diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index 4dcf4d819..23833f88c 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -310,7 +310,10 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): else: x = "" 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: u["cur_moy_ue_txt"] = "bonus de %.3g points" % x u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"]) @@ -380,7 +383,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): ) else: 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" % ( scu.RANG_ATTENTE_STR, 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 # Accès par matieres - # voir si on supporte encore cela en #sco92 XXX - # I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid)) + # En #sco92, pas d'information + I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid)) # 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 if bul_show_mod_rangs and mod["mod_moy_txt"] != "-" and not is_malus: rg = nt.mod_rangs[modimpl["moduleimpl_id"]] - if mod_attente: # nt.get_moduleimpls_attente(): - mod["mod_rang"] = scu.RANG_ATTENTE_STR + if rg[0] is None: + mod["mod_rang_txt"] = "" 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"]) + if mod_attente: # nt.get_moduleimpls_attente(): + mod["mod_rang"] = scu.RANG_ATTENTE_STR + 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: mod["mod_rang_txt"] = "" if mod_attente: