diff --git a/app/comp/res_common.py b/app/comp/res_common.py index f13201262..7fa75c1b7 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -9,18 +9,22 @@ from functools import cached_property import numpy as np import pandas as pd +from flask import g, url_for + from app import log from app.comp.aux_stats import StatsMoyenne from app.comp import moy_sem from app.comp.res_cache import ResultatsCache from app.comp import res_sem from app.comp.moy_mod import ModuleImplResults -from app.models import FormSemestre, Identite, ModuleImpl -from app.models import FormSemestreUECoef +from app.models import FormSemestre, FormSemestreUECoef +from app.models import Identite +from app.models import ModuleImpl, ModuleImplInscription from app.models.ues import UniteEns from app.scodoc import sco_utils as scu from app.scodoc.sco_cache import ResultatsSemestreCache from app.scodoc.sco_codes_parcours import UE_SPORT, DEF +from app.scodoc.sco_exceptions import ScoValueError # Il faut bien distinguer # - ce qui est caché de façon persistente (via redis): @@ -206,12 +210,18 @@ class ResultatsSemestre(ResultatsCache): 0.0, min(self.etud_moy_gen[etudid], 20.0) ) - def _get_etud_ue_cap(self, etudid, ue): - """""" + def _get_etud_ue_cap(self, etudid: int, ue: UniteEns) -> dict: + """Donne les informations sur la capitalisation de l'UE ue pour cet étudiant. + Résultat: + Si pas capitalisée: None + Si capitalisée: un dict, avec les colonnes de validation. + """ capitalisations = self.validations.ue_capitalisees.loc[etudid] if isinstance(capitalisations, pd.DataFrame): ue_cap = capitalisations[capitalisations["ue_code"] == ue.ue_code] - if isinstance(ue_cap, pd.DataFrame) and not ue_cap.empty: + if ue_cap.empty: + return None + if isinstance(ue_cap, pd.DataFrame): # si plusieurs fois capitalisée, prend le max cap_idx = ue_cap["moy_ue"].values.argmax() ue_cap = ue_cap.iloc[cap_idx] @@ -219,8 +229,9 @@ class ResultatsSemestre(ResultatsCache): if capitalisations["ue_code"] == ue.ue_code: ue_cap = capitalisations else: - ue_cap = None - return ue_cap + return None + # converti la Series en dict, afin que les np.int64 reviennent en int + return ue_cap.to_dict() def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict: """L'état de l'UE pour cet étudiant. @@ -253,17 +264,41 @@ class ResultatsSemestre(ResultatsCache): ) if etudid in self.validations.ue_capitalisees.index: ue_cap = self._get_etud_ue_cap(etudid, ue) - if ( - ue_cap is not None - and not ue_cap.empty - and not np.isnan(ue_cap["moy_ue"]) - ): + if ue_cap and not np.isnan(ue_cap["moy_ue"]): was_capitalized = True if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue): moy_ue = ue_cap["moy_ue"] is_capitalized = True - coef_ue = self.etud_coef_ue_df[ue_id][etudid] + # Coef l'UE dans le semestre courant: + if self.is_apc: + # utilise les ECTS comme coef. + coef_ue = ue.ects + else: + # formations classiques + coef_ue = self.etud_coef_ue_df[ue_id][etudid] + if (not coef_ue) and is_capitalized: # étudiant non inscrit dans l'UE courante + if self.is_apc: + # Coefs de l'UE capitalisée en formation APC: donné par ses ECTS + ue_capitalized = UniteEns.query.get(ue_cap["ue_id"]) + coef_ue = ue_capitalized.ects + if coef_ue is None: + orig_sem = FormSemestre.query.get(ue_cap["formsemestre_id"]) + raise ScoValueError( + f"""L'UE capitalisée {ue_capitalized.acronyme} + du semestre {orig_sem.titre_annee()} + n'a pas d'indication d'ECTS. + Corrigez ou faite corriger le programme + via cette page. + """ + ) + else: + # Coefs de l'UE capitalisée en formation classique: + # va chercher le coef dans le semestre d'origine + coef_ue = ModuleImplInscription.sum_coefs_modimpl_ue( + ue_cap["formsemestre_id"], etudid, ue_cap["ue_id"] + ) return { "is_capitalized": is_capitalized, diff --git a/app/models/moduleimpls.py b/app/models/moduleimpls.py index 700dec26e..0aa74ef4b 100644 --- a/app/models/moduleimpls.py +++ b/app/models/moduleimpls.py @@ -2,6 +2,7 @@ """ScoDoc models: moduleimpls """ import pandas as pd +import flask_sqlalchemy from app import db from app.comp import df_cache @@ -129,14 +130,36 @@ class ModuleImplInscription(db.Model): ) @classmethod - def nb_inscriptions_dans_ue( + def etud_modimpls_in_ue( cls, formsemestre_id: int, etudid: int, ue_id: int - ) -> int: - """Nombre de moduleimpls de l'UE auxquels l'étudiant est inscrit""" + ) -> flask_sqlalchemy.BaseQuery: + """moduleimpls de l'UE auxquels l'étudiant est inscrit""" return ModuleImplInscription.query.filter( ModuleImplInscription.etudid == etudid, ModuleImplInscription.moduleimpl_id == ModuleImpl.id, ModuleImpl.formsemestre_id == formsemestre_id, ModuleImpl.module_id == Module.id, Module.ue_id == ue_id, - ).count() + ) + + @classmethod + def nb_inscriptions_dans_ue( + cls, formsemestre_id: int, etudid: int, ue_id: int + ) -> int: + """Nombre de moduleimpls de l'UE auxquels l'étudiant est inscrit""" + return cls.etud_modimpls_in_ue(formsemestre_id, etudid, ue_id).count() + + @classmethod + def sum_coefs_modimpl_ue( + cls, formsemestre_id: int, etudid: int, ue_id: int + ) -> float: + """Somme des coefficients des modules auxquels l'étudiant est inscrit + dans l'UE du semestre indiqué. + N'utilise que les coefficients, donc inadapté aux formations APC. + """ + return sum( + [ + inscr.modimpl.module.coefficient + for inscr in cls.etud_modimpls_in_ue(formsemestre_id, etudid, ue_id) + ] + ) diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index ca6748de7..a5d84cf4f 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -291,15 +291,17 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): I["matieres_modules"] = {} I["matieres_modules_capitalized"] = {} for ue in ues: + u = ue.copy() + ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) if ( ModuleImplInscription.nb_inscriptions_dans_ue( formsemestre_id, etudid, ue["ue_id"] ) == 0 - ): + ) and not ue_status["is_capitalized"]: + # saute les UE où l'on est pas inscrit et n'avons pas de capitalisation continue - u = ue.copy() - ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) + u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...} if ue["type"] != sco_codes_parcours.UE_SPORT: u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"]) diff --git a/app/scodoc/sco_codes_parcours.py b/app/scodoc/sco_codes_parcours.py index f6ca2ff29..bcd6522b1 100644 --- a/app/scodoc/sco_codes_parcours.py +++ b/app/scodoc/sco_codes_parcours.py @@ -282,7 +282,7 @@ class TypeParcours(object): return [ ue_status for ue_status in ues_status - if ue_status["coef_ue"] > 0 + if ue_status["coef_ue"] and isinstance(ue_status["moy"], float) and ue_status["moy"] < self.get_barre_ue(ue_status["ue"]["type"]) ] diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 2c8465150..4bfa7b725 100644 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -1078,7 +1078,7 @@ def formsemestre_status(formsemestre_id=None): "
", ] - if use_ue_coefs: + if use_ue_coefs and not formsemestre.formation.is_apc(): H.append( """utilise les coefficients d'UE pour calculer la moyenne générale.
diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index 987dc962f..c83e1cc4d 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -585,15 +585,17 @@ def formsemestre_recap_parcours_table( else: H.append('