ScoDoc-Lille/app/pe/pe_sxtag.py

262 lines
9.7 KiB
Python

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
"""
Created on Fri Sep 9 09:15:05 2016
@author: barasc
"""
from app.comp.res_sem import load_formsemestre_results
from app.models import UniteEns, FormSemestre
from app.pe import pe_affichage
from app.pe.pe_ressemtag import ResSemBUTTag
import pandas as pd
import numpy as np
from app.pe.pe_tabletags import TableTag
from app.pe.pe_moytag import MoyennesTag
class SxTag(TableTag):
def __init__(self, sxtag_id: (int, int), ressembuttags: dict[int, ResSemBUTTag]):
"""Calcule les moyennes/classements par tag d'un semestre de type 'Sx'
(par ex. 'S1', 'S2', ...) avec une orientation par UE :
* pour les étudiants non redoublants, ce sont les moyennes/classements
du semestre suivi
* pour les étudiants redoublants, c'est une fusion des moyennes/classements
dans les (2) 'Sx' qu'il a suivi
Un SxTag peut donc regrouper plusieurs semestres.
Un SxTag est identifié par un tuple (x, fid) où x est le numéro (semestre_id)
du semestre et fid le formsemestre_id du semestre final (le plus récent) du
regrouprement.
Les **tags**, les **UE** et les inscriptions aux UEs (pour les etudiants)
considérés sont uniquement ceux du semestre final.
Args:
sxtag_id: L'identifiant de SxTag
ressembuttags: Un dictionnaire de la forme `{fid: ResSemBUTTag(fid)}` donnant
les semestres à regrouper et les résultats/moyennes par tag des
semestres
"""
TableTag.__init__(self)
assert sxtag_id and len(sxtag_id) == 2 and sxtag_id[1] in ressembuttags
self.sxtag_id: (int, int) = sxtag_id
"""Identifiant du SxTag de la forme (semestre_id, fid_semestre_final)"""
self.ressembuttags = ressembuttags
"""Les ResSemBUTTags à regrouper dans le SxTag"""
# Les données du semestre final
self.fid_final = sxtag_id[1]
self.ressembuttag_final = ressembuttags[self.fid_final]
"""Le ResSemBUTTag final"""
# Les étudiants (etuds, états civils & etudis)
self.etuds = ressembuttags[self.fid_final].etuds
self.add_etuds(self.etuds)
# Affichage
pe_affichage.pe_print(f"--> {self.get_repr()}")
# Les tags
self.tags_sorted = self.ressembuttag_final.tags_sorted
"""Tags (extraits uniquement du semestre final)"""
pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}")
# Les UE
moy_sem_final = self.ressembuttag_final.moyennes_tags["but"]
self.ues = list(moy_sem_final.matrice_notes.columns)
# Les acronymes des UE
self.acronymes_ues_sorted = sorted(self.ues)
# Les inscriptions des étudiants aux UEs
# => ne conserve que les UEs du semestre final (pour les redoublants)
self.matrice_coeffs = self.ressembuttag_final.moyennes_tags[
"but"
].matrice_coeffs
self.ues_inscr_parcours = ~np.isnan(self.matrice_coeffs.to_numpy())
# Les moyennes par tag
self.moyennes_tags: dict[str, pd.DataFrame] = {}
"""Les notes aux UEs dans différents tags"""
# Masque des inscriptions
inscr_mask = self.ues_inscr_parcours
for tag in self.tags_sorted:
# Cube de note etudids x UEs
notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues_sorted)
# Calcule des moyennes sous forme d'un dataframe"""
matrice_moys_ues = compute_notes_ues(
notes_cube,
self.etudids,
self.acronymes_ues_sorted,
inscr_mask,
)
# Les profils d'ects (pour debug)
profils_ects = []
for i in self.matrice_coeffs.index:
val = tuple(self.matrice_coeffs.loc[i].fillna("x"))
if tuple(val) not in profils_ects:
profils_ects.append(tuple(val))
# Les moyennes
self.moyennes_tags[tag] = MoyennesTag(tag,
matrice_moys_ues,
self.matrice_coeffs)
pe_affichage.pe_print(f"> MoyTag pour {tag} avec")
pe_affichage.pe_print(f" - ues={self.acronymes_ues_sorted}")
pe_affichage.pe_print(f" - ects={profils_ects}")
def __eq__(self, other):
"""Egalité de 2 SxTag sur la base de leur identifiant"""
return self.sxtag_id == other.sxtag_id
def get_repr(self, verbose=False) -> str:
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
est basée)"""
affichage = [str(fid) for fid in self.ressembuttags]
return f"{self.sxtag_id[0]}Tag ({'+'.join(affichage)})"
def compute_notes_ues_cube(self, tag, acronymes_ues_sorted):
"""Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé)
nécessaire au calcul des moyennes du tag pour le RCS Sx
"""
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
etudids = [etud.etudid for etud in self.etuds]
# acronymes_ues = sorted([ue.acronyme for ue in self.ues.values()])
semestres_id = list(self.ressembuttags.keys())
dfs = {}
for frmsem_id in semestres_id:
# Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids, columns=acronymes_ues_sorted)
# Charge les notes du semestre tag
sem_tag = self.ressembuttags[frmsem_id]
moys_tag = sem_tag.moyennes_tags[tag]
notes = moys_tag.matrice_notes # dataframe etudids x ues
acronymes_ues_sem = list(
notes.columns
) # les acronymes des UEs du semestre tag
# UEs communes à celles du SemTag (celles du dernier semestre du RCS)
ues_communes = list(set(acronymes_ues_sorted) & set(acronymes_ues_sem))
# Etudiants communs
etudids_communs = df.index.intersection(notes.index)
# Recopie
df.loc[etudids_communs, ues_communes] = notes.loc[
etudids_communs, ues_communes
]
# Supprime tout ce qui n'est pas numérique
for col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce")
# Stocke le df
dfs[frmsem_id] = df
"""Réunit les notes sous forme d'un cube etudids x ues x semestres"""
semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs]
etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1)
return etudids_x_ues_x_semestres
def compute_notes_ues(
set_cube: np.array,
etudids: list,
acronymes_ues: list,
inscr_mask: np.array,
):
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
par UE) obtenue par un étudiant à un semestre.
Args:
set_cube: notes moyennes aux modules ndarray
(semestre_ids x etudids x UEs), des floats avec des NaN
etudids: liste des étudiants (dim. 0 du cube)
acronymes_ues: liste des acronymes des ues (dim. 1 du cube)
inscr_mask: masque etudids x UE traduisant les inscriptions des
étudiants aux UE (du semestre terminal)
Returns:
Un DataFrame avec pour columns les moyennes par ues,
et pour rows les etudid
"""
nb_etuds, nb_ues, nb_semestres = set_cube.shape
nb_etuds_mask, nb_ues_mask = inscr_mask.shape
assert nb_etuds == len(etudids)
assert nb_ues == len(acronymes_ues)
assert nb_etuds == nb_etuds_mask
assert nb_ues == nb_ues_mask
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
# Entrées à garder dans le cube en fonction du mask d'inscription
inscr_mask_3D = np.stack([inscr_mask] * nb_semestres, axis=-1)
set_cube = set_cube * inscr_mask_3D
# Enlève les NaN du cube pour les entrées manquantes : NaN -> -1.0
set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0)
# Les moyennes par ues
# TODO: Pour l'instant un max sans prise en compte des UE capitalisées
etud_moy = np.max(set_cube_no_nan, axis=2)
# Fix les max non calculé -1 -> NaN
etud_moy[etud_moy < 0] = np.NaN
# Le dataFrame
etud_moy_tag_df = pd.DataFrame(
etud_moy,
index=etudids, # les etudids
columns=acronymes_ues, # les tags
)
etud_moy_tag_df.fillna(np.nan)
return etud_moy_tag_df