forked from ScoDoc/ScoDoc
467 lines
18 KiB
Python
467 lines
18 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.models import FormSemestre
|
|
from app.pe import pe_affichage
|
|
import pandas as pd
|
|
import numpy as np
|
|
from app.pe.rcss import pe_rcs, pe_rcsemx
|
|
import app.pe.moys.pe_sxtag as pe_sxtag
|
|
import app.pe.pe_comp as pe_comp
|
|
from app.pe.moys import pe_tabletags, pe_moytag
|
|
from app.scodoc.sco_utils import ModuleType
|
|
|
|
|
|
class RCSemXTag(pe_tabletags.TableTag):
|
|
def __init__(
|
|
self,
|
|
rcsemx: pe_rcsemx.RCSemX,
|
|
sxstags: dict[(str, int) : pe_sxtag.SxTag],
|
|
semXs_suivis: dict[int, dict],
|
|
):
|
|
"""Calcule les moyennes par tag (orientées compétences)
|
|
d'un regroupement de SxTag
|
|
(RCRCF), pour extraire les classements par tag pour un
|
|
groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous
|
|
participé au même semestre terminal.
|
|
|
|
Args:
|
|
rcsemx: Le RCSemX (identifié par un nom et l'id de son semestre terminal)
|
|
sxstags: Les données sur les SemX taggués
|
|
semXs_suivis: Les données indiquant quels SXTags sont à prendre en compte
|
|
pour chaque étudiant
|
|
"""
|
|
pe_tabletags.TableTag.__init__(self)
|
|
|
|
self.rcs_id: tuple(str, int) = rcsemx.rcs_id
|
|
"""Identifiant du RCSemXTag (identique au RCSemX sur lequel il s'appuie)"""
|
|
|
|
self.rcsemx: pe_rcsemx.RCSemX = rcsemx
|
|
"""Le regroupement RCSemX associé au RCSemXTag"""
|
|
|
|
self.semXs_suivis = semXs_suivis
|
|
"""Les semXs suivis par les étudiants"""
|
|
|
|
self.nom = self.get_repr()
|
|
"""Représentation textuelle du RSCtag"""
|
|
|
|
# Les données du semestre final
|
|
self.formsemestre_final: FormSemestre = rcsemx.formsemestre_final
|
|
"""Le semestre final"""
|
|
self.fid_final: int = rcsemx.formsemestre_final.formsemestre_id
|
|
"""Le fid du semestre final"""
|
|
|
|
# Affichage pour debug
|
|
pe_affichage.pe_print(f"*** {self.get_repr(verbose=True)}")
|
|
|
|
# Les données aggrégés (RCRCF + SxTags)
|
|
self.semXs_aggreges: dict[(str, int) : pe_rcsemx.RCSemX] = rcsemx.semXs_aggreges
|
|
"""Les SemX aggrégés"""
|
|
self.sxstags_aggreges = {}
|
|
"""Les SxTag associés aux SemX aggrégés"""
|
|
try:
|
|
for rcf_id in self.semXs_aggreges:
|
|
self.sxstags_aggreges[rcf_id] = sxstags[rcf_id]
|
|
except:
|
|
raise ValueError("Semestres SxTag manquants")
|
|
self.sxtags_connus = sxstags # Tous les sxstags connus
|
|
|
|
# Les étudiants (etuds, états civils & etudis)
|
|
sems_dans_aggregat = rcsemx.aggregat
|
|
sxtag_final = self.sxstags_aggreges[(sems_dans_aggregat[-1], self.rcs_id[1])]
|
|
self.etuds = sxtag_final.etuds
|
|
"""Les étudiants (extraits du semestre final)"""
|
|
self.add_etuds(self.etuds)
|
|
self.etudids_sorted = sorted(self.etudids)
|
|
"""Les étudids triés"""
|
|
|
|
# Les compétences (extraites de tous les Sxtags)
|
|
self.acronymes_ues_to_competences = self._do_acronymes_to_competences()
|
|
"""L'association acronyme d'UEs -> compétence (extraites des SxTag aggrégés)"""
|
|
|
|
self.competences_sorted = sorted(
|
|
set(self.acronymes_ues_to_competences.values())
|
|
)
|
|
"""Compétences (triées par nom, extraites des SxTag aggrégés)"""
|
|
aff = pe_affichage.repr_comp_et_ues(self.acronymes_ues_to_competences)
|
|
pe_affichage.pe_print(f"--> Compétences : {', '.join(self.competences_sorted)}")
|
|
|
|
# Les tags
|
|
self.tags_sorted = self._do_taglist()
|
|
"""Tags extraits de tous les SxTag aggrégés"""
|
|
aff_tag = ["👜" + tag for tag in self.tags_sorted]
|
|
pe_affichage.pe_print(f"--> Tags : {', '.join(aff_tag)}")
|
|
|
|
# Les moyennes
|
|
self.moyennes_tags: dict[str, pe_moytag.MoyennesTag] = {}
|
|
|
|
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
|
|
for tag in self.tags_sorted:
|
|
pe_affichage.pe_print(f"--> Moyennes du tag 👜{tag}")
|
|
|
|
# Traitement des inscriptions aux semX(tags)
|
|
# ******************************************
|
|
# Cube d'inscription (etudids_sorted x compétences_sorted x sxstags)
|
|
# indiquant quel sxtag est valide pour chaque étudiant
|
|
inscr_df, inscr_cube = self.compute_inscriptions_comps_cube(tag)
|
|
|
|
# Traitement des notes
|
|
# ********************
|
|
# Cube de notes (etudids_sorted x compétences_sorted x sxstags)
|
|
notes_df, notes_cube = self.compute_notes_comps_cube(tag)
|
|
# Calcule les moyennes sous forme d'un dataframe en les "aggrégant"
|
|
# compétence par compétence
|
|
moys_competences = self.compute_notes_competences(notes_cube, inscr_cube)
|
|
|
|
# Traitement des coeffs pour la moyenne générale
|
|
# ***********************************************
|
|
# Df des coeffs sur tous les SxTags aggrégés
|
|
coeffs_df, coeffs_cube = self.compute_coeffs_comps_cube(tag)
|
|
|
|
# Synthèse des coefficients à prendre en compte pour la moyenne générale
|
|
matrice_coeffs_moy_gen = self.compute_coeffs_competences(
|
|
coeffs_cube, inscr_cube, notes_cube
|
|
)
|
|
|
|
# Affichage des coeffs
|
|
aff = pe_affichage.repr_profil_coeffs(
|
|
matrice_coeffs_moy_gen, with_index=True
|
|
)
|
|
pe_affichage.pe_print(f" > Moyenne calculée avec pour coeffs : {aff}")
|
|
|
|
# Mémorise les moyennes et les coeff associés
|
|
self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
|
|
tag,
|
|
pe_moytag.CODE_MOY_COMPETENCES,
|
|
moys_competences,
|
|
matrice_coeffs_moy_gen,
|
|
)
|
|
|
|
def __eq__(self, other):
|
|
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
|
|
return self.rcs_id == other.sxtag_id
|
|
|
|
def get_repr(self, verbose=True) -> str:
|
|
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
|
|
est basée)"""
|
|
if verbose:
|
|
return f"{self.__class__.__name__} basé sur " + self.rcsemx.get_repr(
|
|
verbose=verbose
|
|
)
|
|
else:
|
|
return f"{self.__class__.__name__} {self.rcs_id}"
|
|
|
|
def compute_notes_comps_cube(self, tag):
|
|
"""Pour un tag donné, construit le cube de notes (etudid x competences x SxTag)
|
|
nécessaire au calcul des moyennes,
|
|
en remplaçant les données d'UE (obtenus du SxTag) par les compétences
|
|
|
|
Args:
|
|
tag: Le tag visé
|
|
"""
|
|
# etudids_sorted: list[int],
|
|
# competences_sorted: list[str],
|
|
# sxstags: dict[(str, int) : pe_sxtag.SxTag],
|
|
notes_dfs = {}
|
|
|
|
for sxtag_id, sxtag in self.sxstags_aggreges.items():
|
|
# Partant d'un dataframe vierge
|
|
notes_df = pd.DataFrame(
|
|
np.nan, index=self.etudids_sorted, columns=self.competences_sorted
|
|
)
|
|
# Charge les notes du semestre tag (copie car changement de nom de colonnes à venir)
|
|
if tag in sxtag.moyennes_tags: # si le tag est présent dans le semestre
|
|
moys_tag = sxtag.moyennes_tags[tag]
|
|
|
|
notes = moys_tag.matrice_notes_gen.copy() # dataframe etudids x ues
|
|
|
|
# Traduction des acronymes d'UE en compétences
|
|
acronymes_ues_columns = notes.columns
|
|
acronymes_to_comps = [
|
|
self.acronymes_ues_to_competences[acro]
|
|
for acro in acronymes_ues_columns
|
|
]
|
|
notes.columns = acronymes_to_comps
|
|
|
|
# Les étudiants et les compétences communes
|
|
(
|
|
etudids_communs,
|
|
comp_communes,
|
|
) = pe_comp.find_index_and_columns_communs(notes_df, notes)
|
|
|
|
# Recopie des notes et des coeffs
|
|
notes_df.loc[etudids_communs, comp_communes] = notes.loc[
|
|
etudids_communs, comp_communes
|
|
]
|
|
|
|
# Supprime tout ce qui n'est pas numérique
|
|
# for col in notes_df.columns:
|
|
# notes_df[col] = pd.to_numeric(notes_df[col], errors="coerce")
|
|
|
|
# Stocke les dfs
|
|
notes_dfs[sxtag_id] = notes_df
|
|
|
|
"""Réunit les notes sous forme d'un cube etudids x competences x semestres"""
|
|
sxtag_x_etudids_x_comps = [
|
|
notes_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
|
|
]
|
|
notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
|
|
|
|
return notes_dfs, notes_etudids_x_comps_x_sxtag
|
|
|
|
def compute_coeffs_comps_cube(self, tag):
|
|
"""Pour un tag donné, construit
|
|
le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions
|
|
des étudiants aux UEs en fonction de leur parcours)
|
|
qui s'applique aux différents SxTag
|
|
en remplaçant les données d'UE (obtenus du SxTag) par les compétences
|
|
|
|
Args:
|
|
tag: Le tag visé
|
|
"""
|
|
# etudids_sorted: list[int],
|
|
# competences_sorted: list[str],
|
|
# sxstags: dict[(str, int) : pe_sxtag.SxTag],
|
|
|
|
coeffs_dfs = {}
|
|
|
|
for sxtag_id, sxtag in self.sxstags_aggreges.items():
|
|
# Partant d'un dataframe vierge
|
|
coeffs_df = pd.DataFrame(
|
|
np.nan, index=self.etudids_sorted, columns=self.competences_sorted
|
|
)
|
|
if tag in sxtag.moyennes_tags:
|
|
moys_tag = sxtag.moyennes_tags[tag]
|
|
|
|
# Charge les notes et les coeffs du semestre tag
|
|
coeffs = moys_tag.matrice_coeffs_moy_gen.copy() # les coeffs
|
|
|
|
# Traduction des acronymes d'UE en compétences
|
|
acronymes_ues_columns = coeffs.columns
|
|
acronymes_to_comps = [
|
|
self.acronymes_ues_to_competences[acro]
|
|
for acro in acronymes_ues_columns
|
|
]
|
|
coeffs.columns = acronymes_to_comps
|
|
|
|
# Les étudiants et les compétences communes
|
|
etudids_communs, comp_communes = pe_comp.find_index_and_columns_communs(
|
|
coeffs_df, coeffs
|
|
)
|
|
|
|
# Recopie des notes et des coeffs
|
|
coeffs_df.loc[etudids_communs, comp_communes] = coeffs.loc[
|
|
etudids_communs, comp_communes
|
|
]
|
|
|
|
# Stocke les dfs
|
|
coeffs_dfs[sxtag_id] = coeffs_df
|
|
|
|
"""Réunit les coeffs sous forme d'un cube etudids x competences x semestres"""
|
|
sxtag_x_etudids_x_comps = [
|
|
coeffs_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
|
|
]
|
|
coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
|
|
|
|
return coeffs_dfs, coeffs_etudids_x_comps_x_sxtag
|
|
|
|
def compute_inscriptions_comps_cube(
|
|
self,
|
|
tag,
|
|
):
|
|
"""Pour un tag donné, construit
|
|
le cube etudid x competences x SxTag traduisant quels sxtags est à prendre
|
|
en compte pour chaque étudiant.
|
|
Contient des 0 et des 1 pour indiquer la prise en compte.
|
|
|
|
Args:
|
|
tag: Le tag visé
|
|
"""
|
|
# etudids_sorted: list[int],
|
|
# competences_sorted: list[str],
|
|
# sxstags: dict[(str, int) : pe_sxtag.SxTag],
|
|
# Initialisation
|
|
inscriptions_dfs = {}
|
|
|
|
for sxtag_id, sxtag in self.sxstags_aggreges.items():
|
|
# Partant d'un dataframe vierge
|
|
inscription_df = pd.DataFrame(
|
|
0, index=self.etudids_sorted, columns=self.competences_sorted
|
|
)
|
|
|
|
# Les étudiants dont les résultats au sxtag ont été calculés
|
|
etudids_sxtag = sxtag.etudids_sorted
|
|
|
|
# Les étudiants communs
|
|
etudids_communs = sorted(set(self.etudids_sorted) & set(etudids_sxtag))
|
|
|
|
# Acte l'inscription
|
|
inscription_df.loc[etudids_communs, :] = 1
|
|
|
|
# Stocke les dfs
|
|
inscriptions_dfs[sxtag_id] = inscription_df
|
|
|
|
"""Réunit les inscriptions sous forme d'un cube etudids x competences x semestres"""
|
|
sxtag_x_etudids_x_comps = [
|
|
inscriptions_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
|
|
]
|
|
inscriptions_etudids_x_comps_x_sxtag = np.stack(
|
|
sxtag_x_etudids_x_comps, axis=-1
|
|
)
|
|
|
|
return inscriptions_dfs, inscriptions_etudids_x_comps_x_sxtag
|
|
|
|
def _do_taglist(self) -> list[str]:
|
|
"""Synthétise les tags à partir des Sxtags aggrégés.
|
|
|
|
Returns:
|
|
Liste de tags triés par ordre alphabétique
|
|
"""
|
|
tags = []
|
|
for frmsem_id in self.sxstags_aggreges:
|
|
tags.extend(self.sxstags_aggreges[frmsem_id].tags_sorted)
|
|
return sorted(set(tags))
|
|
|
|
def _do_acronymes_to_competences(self) -> dict[str:str]:
|
|
"""Synthétise l'association complète {acronyme_ue: competences}
|
|
extraite de toutes les données/associations des SxTags
|
|
aggrégés.
|
|
|
|
Returns:
|
|
Un dictionnaire {'acronyme_ue' : 'compétences'}
|
|
"""
|
|
dict_competences = {}
|
|
for sxtag_id, sxtag in self.sxstags_aggreges.items():
|
|
dict_competences |= sxtag.acronymes_ues_to_competences
|
|
return dict_competences
|
|
|
|
def compute_notes_competences(self, set_cube: np.array, inscriptions: np.array):
|
|
"""Calcule la moyenne par compétences (à un tag donné) sur plusieurs semestres (partant du set_cube).
|
|
|
|
La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles
|
|
|
|
*Remarque* : Adaptation de moy_ue.compute_ue_moys_apc au cas des moyennes de tag
|
|
par aggrégat de plusieurs semestres.
|
|
|
|
Args:
|
|
set_cube: notes moyennes aux compétences ndarray
|
|
(etuds x UEs|compétences x sxtags), des floats avec des NaN
|
|
inscriptions: inscrptions aux compétences ndarray
|
|
(etuds x UEs|compétences x sxtags), des 0 et des 1
|
|
Returns:
|
|
Un DataFrame avec pour columns les moyennes par tags,
|
|
et pour rows les etudid
|
|
"""
|
|
# etudids_sorted: liste des étudiants (dim. 0 du cube)
|
|
# competences_sorted: list (dim. 1 du cube)
|
|
nb_etuds, nb_comps, nb_semestres = set_cube.shape
|
|
# assert nb_etuds == len(etudids_sorted)
|
|
# assert nb_comps == len(competences_sorted)
|
|
|
|
# Applique le masque d'inscriptions
|
|
set_cube_significatif = set_cube * inscriptions
|
|
|
|
# Quelles entrées du cube contiennent des notes ?
|
|
mask = ~np.isnan(set_cube_significatif)
|
|
|
|
# Enlève les NaN du cube de notes pour les entrées manquantes
|
|
set_cube_no_nan = np.nan_to_num(set_cube_significatif, nan=0.0)
|
|
|
|
# Les moyennes par tag
|
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
|
etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2)
|
|
|
|
# Le dataFrame des notes moyennes
|
|
etud_moy_tag_df = pd.DataFrame(
|
|
etud_moy_tag,
|
|
index=self.etudids_sorted, # les etudids
|
|
columns=self.competences_sorted, # les competences
|
|
)
|
|
etud_moy_tag_df.fillna(np.nan)
|
|
|
|
return etud_moy_tag_df
|
|
|
|
def compute_coeffs_competences(
|
|
self,
|
|
coeff_cube: np.array,
|
|
inscriptions: np.array,
|
|
set_cube: np.array,
|
|
):
|
|
"""Calcule les coeffs à utiliser pour la moyenne générale (toutes compétences
|
|
confondues), en fonction des inscriptions.
|
|
|
|
Args:
|
|
coeffs_cube: coeffs impliqués dans la moyenne générale (semestres par semestres)
|
|
inscriptions: inscriptions aux UES|Compétences ndarray
|
|
(etuds x UEs|compétences x sxtags), des 0 ou des 1
|
|
set_cube: les notes
|
|
|
|
|
|
Returns:
|
|
Un DataFrame de coefficients (etudids_sorted x compétences_sorted)
|
|
"""
|
|
# etudids_sorted: liste des étudiants (dim. 0 du cube)
|
|
# competences_sorted: list (dim. 1 du cube)
|
|
nb_etuds, nb_comps, nb_semestres = inscriptions.shape
|
|
# assert nb_etuds == len(etudids_sorted)
|
|
# assert nb_comps == len(competences_sorted)
|
|
|
|
# Applique le masque des inscriptions aux coeffs et aux notes
|
|
coeffs_significatifs = coeff_cube * inscriptions
|
|
|
|
# Enlève les NaN du cube de notes pour les entrées manquantes
|
|
coeffs_cube_no_nan = np.nan_to_num(coeffs_significatifs, nan=0.0)
|
|
|
|
# Quelles entrées du cube contiennent des notes ?
|
|
mask = ~np.isnan(set_cube)
|
|
|
|
# Retire les coefficients associés à des données sans notes
|
|
coeffs_cube_no_nan = coeffs_cube_no_nan * mask
|
|
|
|
# Somme les coefficients (correspondant à des notes)
|
|
coeff_tag = np.sum(coeffs_cube_no_nan, axis=2)
|
|
|
|
# Le dataFrame des coeffs
|
|
coeffs_df = pd.DataFrame(
|
|
coeff_tag, index=self.etudids_sorted, columns=self.competences_sorted
|
|
)
|
|
# Remet à Nan les coeffs à 0
|
|
coeffs_df = coeffs_df.fillna(np.nan)
|
|
|
|
return coeffs_df
|