ScoDocMM/app/comp/res_but.py

233 lines
9.3 KiB
Python
Raw Normal View History

2021-12-30 23:58:38 +01:00
##############################################################################
# ScoDoc
2022-01-01 14:51:28 +01:00
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
2021-12-30 23:58:38 +01:00
# See LICENSE
##############################################################################
"""Résultats semestres BUT
"""
from collections.abc import Generator
import time
2022-02-08 00:04:07 +01:00
import numpy as np
2022-01-16 23:47:52 +01:00
import pandas as pd
2021-12-30 23:58:38 +01:00
from app import log
2021-12-30 23:58:38 +01:00
from app.comp import moy_ue, moy_sem, inscr_mod
from app.comp.res_compat import NotesTableCompat
2022-01-25 10:45:13 +01:00
from app.comp.bonus_spo import BonusSport
2022-03-11 16:03:06 +01:00
from app.models import ScoDocSiteConfig
from app.models.moduleimpls import ModuleImpl
from app.models.ues import UniteEns
2022-01-25 10:45:13 +01:00
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc import sco_preferences
2021-12-30 23:58:38 +01:00
class ResultatsSemestreBUT(NotesTableCompat):
"""Résultats BUT: organisation des calculs"""
_cached_attrs = NotesTableCompat._cached_attrs + (
"modimpl_coefs_df",
"modimpls_evals_poids",
"sem_cube",
"etuds_parcour_id", # parcours de chaque étudiant
"ues_inscr_parcours_df", # inscriptions aux UE / parcours
2021-12-30 23:58:38 +01:00
)
def __init__(self, formsemestre):
super().__init__(formsemestre)
2022-04-02 13:30:26 +02:00
2022-03-26 23:33:57 +01:00
self.sem_cube = None
"""ndarray (etuds x modimpl x ue)"""
self.etuds_parcour_id = None
"""Parcours de chaque étudiant { etudid : parcour_id }"""
2021-12-30 23:58:38 +01:00
if not self.load_cached():
t0 = time.time()
2021-12-30 23:58:38 +01:00
self.compute()
t1 = time.time()
2021-12-30 23:58:38 +01:00
self.store()
t2 = time.time()
log(
2022-04-02 13:30:26 +02:00
f"""ResultatsSemestreBUT: cached formsemestre_id={formsemestre.id
} ({(t1-t0):g}s +{(t2-t1):g}s)"""
)
2021-12-30 23:58:38 +01:00
def compute(self):
"Charge les notes et inscriptions et calcule les moyennes d'UE et gen."
(
self.sem_cube,
self.modimpls_evals_poids,
self.modimpls_results,
) = moy_ue.notes_sem_load_cube(self.formsemestre)
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
2021-12-30 23:58:38 +01:00
self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
self.formsemestre, modimpls=self.formsemestre.modimpls_sorted
2021-12-30 23:58:38 +01:00
)
# 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)
2022-01-25 10:45:13 +01:00
2022-02-17 23:13:55 +01:00
# Masque de tous les modules _sauf_ les bonus (sport)
modimpls_mask = [
modimpl.module.ue.type != UE_SPORT
for modimpl in self.formsemestre.modimpls_sorted
]
2021-12-30 23:58:38 +01:00
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
self.sem_cube,
self.etuds,
2022-01-25 10:45:13 +01:00
self.formsemestre.modimpls_sorted,
2021-12-30 23:58:38 +01:00
self.ues,
self.modimpl_inscr_df,
self.modimpl_coefs_df,
2022-02-17 23:13:55 +01:00
modimpls_mask,
2021-12-30 23:58:38 +01:00
)
2022-01-16 23:47:52 +01:00
# Les coefficients d'UE ne sont pas utilisés en APC
self.etud_coef_ue_df = pd.DataFrame(
2022-02-27 20:32:38 +01:00
0.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
2022-01-16 23:47:52 +01:00
)
2022-01-26 22:59:53 +01:00
2022-02-01 11:37:05 +01:00
# --- Modules de MALUS sur les UEs
self.malus = moy_ue.compute_malus(
self.formsemestre, self.sem_cube, self.ues, self.modimpl_inscr_df
)
self.etud_moy_ue -= self.malus
2022-01-25 10:45:13 +01:00
# --- Bonus Sport & Culture
2022-02-17 23:13:55 +01:00
if not all(modimpls_mask): # au moins un module bonus
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
# Clippe toutes les moyennes d'UE dans [0,20]
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
2022-01-25 10:45:13 +01:00
# Nanifie les moyennes d'UE hors parcours pour chaque étudiant
self.etud_moy_ue *= self.ues_inscr_parcours_df
2022-01-26 22:59:53 +01:00
# Moyenne générale indicative:
# (note: le bonus sport a déjà été appliqué aux moyennes d'UE, et impacte
# donc la moyenne indicative)
2022-02-27 20:32:38 +01:00
# self.etud_moy_gen = moy_sem.compute_sem_moys_apc_using_coefs(
# self.etud_moy_ue, self.modimpl_coefs_df
# )
self.etud_moy_gen = moy_sem.compute_sem_moys_apc_using_ects(
self.etud_moy_ue,
[ue.ects for ue in self.ues if ue.type != UE_SPORT],
2022-02-27 20:32:38 +01:00
formation_id=self.formsemestre.formation_id,
skip_empty_ues=sco_preferences.get_preference(
"but_moy_skip_empty_ues", self.formsemestre.id
),
2022-01-26 22:59:53 +01:00
)
# --- UE capitalisées
self.apply_capitalisation()
2022-02-06 18:29:22 +01:00
# --- Classements:
self.compute_rangs()
2021-12-30 23:58:38 +01:00
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
"""La moyenne de l'étudiant dans le moduleimpl
En APC, il s'agit d'une moyenne indicative sans valeur.
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM)
"""
mod_idx = self.modimpl_coefs_df.columns.get_loc(moduleimpl_id)
etud_idx = self.etud_index[etudid]
# moyenne sur les UE:
2022-02-09 00:36:50 +01:00
if len(self.sem_cube[etud_idx, mod_idx]):
return np.nanmean(self.sem_cube[etud_idx, mod_idx])
return np.nan
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.
En BUT, c'est simple: Coef = somme des coefs des modules vers cette UE.
(ne dépend pas des modules auxquels est inscrit l'étudiant, ).
"""
return self.modimpl_coefs_df.loc[ue.id].sum()
2022-03-11 16:03:06 +01:00
2022-03-26 23:33:57 +01:00
def modimpls_in_ue(self, ue_id, etudid, with_bonus=True) -> list[ModuleImpl]:
2022-03-11 16:03:06 +01:00
"""Liste des modimpl ayant des coefs non nuls vers cette UE
2022-03-26 23:33:57 +01:00
et auxquels l'étudiant est inscrit. Inclus modules bonus le cas échéant.
2022-03-11 16:03:06 +01:00
"""
2022-03-26 23:33:57 +01:00
# sert pour l'affichage ou non de l'UE sur le bulletin et la table recap
2022-03-11 16:03:06 +01:00
coefs = self.modimpl_coefs_df # row UE, cols modimpl
2022-03-26 23:33:57 +01:00
modimpls = [
2022-03-11 16:03:06 +01:00
modimpl
for modimpl in self.formsemestre.modimpls_sorted
if (coefs[modimpl.id][ue_id] != 0)
and self.modimpl_inscr_df[modimpl.id][etudid]
]
2022-03-26 23:33:57 +01:00
if not with_bonus:
return [
modimpl for modimpl in modimpls if modimpl.module.ue.type != UE_SPORT
]
return modimpls
def modimpl_notes(self, modimpl_id: int, ue_id: int) -> np.ndarray:
"""Les notes moyennes des étudiants du sem. à ce modimpl dans cette ue.
Utile pour stats bottom tableau recap.
Résultat: 1d array of float
2022-03-26 23:33:57 +01:00
"""
i = self.modimpl_coefs_df.columns.get_loc(modimpl_id)
j = self.modimpl_coefs_df.index.get_loc(ue_id)
return self.sem_cube[:, i, j]
def load_ues_inscr_parcours(self) -> pd.DataFrame:
"""Chargement des inscriptions aux parcours et calcul de la
matrice d'inscriptions (etuds, ue).
S'il n'y pas de référentiel de compétence, donc pas de parcours,
on considère l'étudiant inscrit à toutes les ue.
La matrice avec ue ne comprend que les UE non bonus.
1.0 si étudiant inscrit à l'UE, NaN sinon.
"""
etuds_parcour_id = {
inscr.etudid: inscr.parcour_id for inscr in self.formsemestre.inscriptions
}
self.etuds_parcour_id = etuds_parcour_id
ue_ids = [ue.id for ue in self.ues]
# matrice de 1, inscrits par défaut à toutes les UE:
ues_inscr_parcours_df = pd.DataFrame(
1.0, index=etuds_parcour_id.keys(), columns=ue_ids, dtype=float
)
if self.formsemestre.formation.referentiel_competence is None:
return ues_inscr_parcours_df
ue_by_parcours = {} # parcours_id : {ue_id:0|1}
for parcour in self.formsemestre.formation.referentiel_competence.parcours:
ue_by_parcours[parcour.id] = {
ue.id: 1.0
for ue in self.formsemestre.formation.query_ues_parcour(
parcour
).filter_by(semestre_idx=self.formsemestre.semestre_id)
}
for etudid in etuds_parcour_id:
parcour = etuds_parcour_id[etudid]
if parcour is not None:
ues_inscr_parcours_df.loc[etudid] = ue_by_parcours[
etuds_parcour_id[etudid]
]
return ues_inscr_parcours_df
def etud_ues_ids(self, etudid: int) -> list[int]:
"""Liste des id d'UE auxquelles l'étudiant est inscrit (sans bonus).
2022-06-09 07:39:58 +02:00
(surchargée ici pour prendre en compte les parcours)
"""
s = self.ues_inscr_parcours_df.loc[etudid]
return s.index[s.notna()]
def etud_ues(self, etudid: int) -> Generator[UniteEns]:
"""Liste des UE auxquelles l'étudiant est inscrit."""
return (UniteEns.query.get(ue_id) for ue_id in self.etud_ues_ids(etudid))