############################################################################## # ScoDoc # Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # See LICENSE ############################################################################## """Résultats semestres classiques (non APC) """ import time import numpy as np import pandas as pd from sqlalchemy.sql import text from flask import g, url_for from app import db from app import log from app.comp import moy_mod, moy_ue, inscr_mod from app.comp.res_common import NotesTableCompat from app.comp.bonus_spo import BonusSport from app.models import ScoDocSiteConfig from app.models.etudiants import Identite from app.models.formsemestre import FormSemestre from app.models.ues import UniteEns from app.scodoc.sco_codes_parcours import UE_SPORT from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_utils import ModuleType class ResultatsSemestreClassic(NotesTableCompat): """Résultats du semestre (formation classique): organisation des calculs.""" _cached_attrs = NotesTableCompat._cached_attrs + ( "modimpl_coefs", "modimpl_idx", "sem_matrix", ) def __init__(self, formsemestre): super().__init__(formsemestre) if not self.load_cached(): t0 = time.time() self.compute() t1 = time.time() self.store() t2 = time.time() log( f"ResultatsSemestreClassic: cached formsemestre_id={formsemestre.id} ({(t1-t0):g}s +{(t2-t1):g}s)" ) # recalculé (aussi rapide que de les cacher) self.moy_min = self.etud_moy_gen.min() self.moy_max = self.etud_moy_gen.max() self.moy_moy = self.etud_moy_gen.mean() def compute(self): "Charge les notes et inscriptions et calcule les moyennes d'UE et gen." self.sem_matrix, self.modimpls_results = notes_sem_load_matrix( self.formsemestre ) self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre) self.modimpl_coefs = np.array( [m.module.coefficient or 0.0 for m in self.formsemestre.modimpls_sorted] ) self.modimpl_idx = { m.id: i for i, m in enumerate(self.formsemestre.modimpls_sorted) } "l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]" modimpl_standards_mask = np.array( [ (m.module.module_type == ModuleType.STANDARD) and (m.module.ue.type != UE_SPORT) for m in self.formsemestre.modimpls_sorted ] ) ( self.etud_moy_gen, self.etud_moy_ue, self.etud_coef_ue_df, ) = moy_ue.compute_ue_moys_classic( self.formsemestre, self.sem_matrix, self.ues, self.modimpl_inscr_df, self.modimpl_coefs, modimpl_standards_mask, ) # --- Modules de MALUS sur les UEs et la moyenne générale self.malus = moy_ue.compute_malus( self.formsemestre, self.sem_matrix, self.ues, self.modimpl_inscr_df ) self.etud_moy_ue -= self.malus # ajuste la moyenne générale (à l'aide des coefs d'UE) self.etud_moy_gen -= (self.etud_coef_ue_df * self.malus).sum( axis=1 ) / self.etud_coef_ue_df.sum(axis=1) # --- Bonus Sport & Culture bonus_class = ScoDocSiteConfig.get_bonus_sport_class() if bonus_class is not None: bonus: BonusSport = bonus_class( self.formsemestre, self.sem_matrix, self.ues, self.modimpl_inscr_df, self.modimpl_coefs, 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) bonus_mg = bonus.get_bonus_moy_gen() if bonus_mg is None and self.bonus_ues is not None: # pas de bonus explicite sur la moyenne générale # on l'ajuste pour refléter les modifs d'UE, à l'aide des coefs d'UE. bonus_mg = (self.etud_coef_ue_df * self.bonus_ues).sum( axis=1 ) / self.etud_coef_ue_df.sum(axis=1) self.etud_moy_gen += bonus_mg elif bonus_mg is not None: # Applique le bonus moyenne générale renvoyé self.etud_moy_gen += bonus_mg self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True) # compat nt, utilisé pour l'afficher sur les bulletins: self.bonus = bonus_mg # --- UE capitalisées self.apply_capitalisation() # --- Classements: self.compute_rangs() def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float: """La moyenne de l'étudiant dans le moduleimpl Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM) """ return self.modimpls_results[moduleimpl_id].etuds_moy_module.get(etudid, "NI") def get_mod_stats(self, moduleimpl_id: int) -> dict: """Stats sur les notes obtenues dans un modimpl""" notes_series: pd.Series = self.modimpls_results[moduleimpl_id].etuds_moy_module nb_notes = len(notes_series) if not nb_notes: super().get_mod_stats(moduleimpl_id) return { # Series: Statistical methods from ndarray have been overridden to automatically # exclude missing data (currently represented as NaN) "moy": notes_series.mean(), # donc sans prendre en compte les NaN "max": notes_series.max(), "min": notes_series.min(), "nb_notes": nb_notes, "nb_missing": sum(notes_series.isna()), "nb_valid_evals": sum( self.modimpls_results[moduleimpl_id].evaluations_completes ), } def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float: """Détermine le coefficient de l'UE pour cet étudiant. N'est utilisé que pour l'injection des UE capitalisées dans la moyenne générale. Coef = somme des coefs des modules de l'UE auxquels il est inscrit """ c = comp_etud_sum_coef_modules_ue(self.formsemestre.id, etudid, ue["ue_id"]) if c is not None: # inscrit à au moins un module de cette UE return c # arfff: aucun moyen de déterminer le coefficient de façon sûre log( "* oups: calcul coef UE impossible\nformsemestre_id='%s'\netudid='%s'\nue=%s" % (self.formsemestre.id, etudid, ue) ) etud: Identite = Identite.query.get(etudid) raise ScoValueError( """
Coefficient de l'UE capitalisée %s impossible à déterminer pour l'étudiant %s
Il faut saisir le coefficient de cette UE avant de continuer