Relecture + améliorations diverses (dont tri systématique par etudids_sorted, acronymes_sorted, competences_sorted) des dataframes

This commit is contained in:
Cléo Baras 2024-02-20 09:13:19 +01:00
parent 8de1a44583
commit 83059cd995
7 changed files with 442 additions and 241 deletions

View File

@ -60,6 +60,7 @@ from app.pe.pe_rcstag import RCSTag
from app.pe.pe_ressemtag import ResSemBUTTag
from app.pe.pe_interclasstag import RCSInterclasseTag
import app.pe.pe_rcss_jury as pe_rcss_jury
import app.pe.rcss.rcss_constantes as rcss_constantes
class JuryPE(object):
@ -107,6 +108,7 @@ class JuryPE(object):
self._gen_xls_diplomes(zipfile)
self._gen_xls_ressembuttags(zipfile)
self._gen_rcss()
self._gen_rcsf()
self._gen_xls_sxtags(zipfile)
self._gen_rcrcfs()
self._gen_xls_rcrcss_tags(zipfile)
@ -166,11 +168,14 @@ class JuryPE(object):
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
onglets = []
for res_sem_tag in self.ressembuttags.values():
onglet = res_sem_tag.get_repr(verbose=False)
onglet = res_sem_tag.get_repr(verbose=True)
onglets += []
df = res_sem_tag.df_moyennes_et_classements()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0)
self.add_file_to_zip(
@ -190,16 +195,19 @@ class JuryPE(object):
self.rcss_jury.cree_rcss(self.etudiants)
def _gen_xls_sxtags(self, zipfile: ZipFile):
"""Génère les semestres taggués en s'appuyant sur les RCS de type Sx (pour
identifier les redoublements impactant les semestres taggués).
"""
def _gen_rcsf(self):
"""Génère les RCF, regroupement de semestres de type Sx pour préparer
le calcul des moyennes par Sx"""
# Génère les regroupements de semestres de type Sx
pe_affichage.pe_print(
"*** Génère les RCSValid (RCS de même Sx donnant lieu à validation du semestre)"
)
self.rcss_jury.cree_rcfs(self.etudiants)
def _gen_xls_sxtags(self, zipfile: ZipFile):
"""Génère les semestres taggués en s'appuyant sur les RCF de type Sx (pour
identifier les redoublements impactant les semestres taggués).
"""
# Génère les moyennes des RCS de type Sx
pe_affichage.pe_print("*** Calcule les moyennes des SxTag")
@ -216,11 +224,15 @@ class JuryPE(object):
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
onglets = []
for sxtag in self.sxtags.values():
onglet = sxtag.get_repr(verbose=False)
onglets += [onglet]
df = sxtag.df_moyennes_et_classements()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0)
self.add_file_to_zip(
@ -269,11 +281,14 @@ class JuryPE(object):
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
onglets = []
for rcs_tag in self.rcss_tags.values():
onglet = rcs_tag.get_repr(verbose=False)
onglets += [onglet]
df = rcs_tag.df_moyennes_et_classements()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0)
self.add_file_to_zip(
@ -296,12 +311,16 @@ class JuryPE(object):
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
onglets = []
for interclass_tag in self.interclassements_taggues.values():
if interclass_tag.significatif: # Avec des notes
onglet = interclass_tag.get_repr()
onglets += [onglet]
df = interclass_tag.df_moyennes_et_classements()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0)
self.add_file_to_zip(
@ -322,9 +341,12 @@ class JuryPE(object):
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
onglets = []
for onglet, df in self.synthese.items():
onglets += [onglet]
# écriture dans l'onglet:
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0)
self.add_file_to_zip(
@ -342,9 +364,12 @@ class JuryPE(object):
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
onglets = []
for onglet, df in synthese.items():
onglets += [onglet]
# écriture dans l'onglet:
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0)
self.add_file_to_zip(
@ -433,7 +458,7 @@ class JuryPE(object):
# Ajout des aggrégats
for aggregat in pe_rcs.TOUS_LES_RCS:
descr = app.pe.rcss.constantes.TYPES_RCS[aggregat]["descr"]
descr = rcss_constantes.TYPES_RCS[aggregat]["descr"]
# Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag
# considéré

View File

@ -3,9 +3,7 @@ import pandas as pd
from app import comp
from app.comp.moy_sem import comp_ranks_series
from app.models import UniteEns
from app.pe import pe_affichage
from app.scodoc.codes_cursus import UE_SPORT
class Moyenne:
@ -171,7 +169,7 @@ class MoyennesTag:
tag: Un tag
matrice_notes: Les moyennes (etudid x acronymes_ues ou etudid x compétences) aux différentes UEs ou compétences
matrice_coeffs: Les coeff à appliquer pour le calcul de la moyenne générale
# notes_gen: Une série de notes (moyenne) sous forme d'un pd.Series() (toutes UEs confondues)
# notes_gen: Une série de notes (moyenne) sous forme d'un ``pd.Series`` (toutes UEs confondues)
"""
self.tag = tag
"""Le tag associé aux moyennes"""
@ -207,7 +205,7 @@ class MoyennesTag:
ont des notes
Returns:
True si a des notes, False sinon
True si la moytag a des notes, False sinon
"""
notes = self.matrice_notes
nbre_nan = notes.isna().sum().sum()

View File

@ -152,7 +152,8 @@ class RCSsJuryPE:
# Ajout du RCRCF
if rcf_id not in self.rcrcfs:
self.rcrcfs[rcf_id] = pe_rcrcf.RCRCF(rcf_id, rcf.formsemestre_final)
rcf_nom = rcf_id[0]
self.rcrcfs[rcf_id] = pe_rcrcf.RCRCF(rcf_nom, rcf.formsemestre_final)
rcrcf = self.rcrcfs[rcf_id]
# Ajout des RCFs au RCRCF

View File

@ -37,6 +37,7 @@ Created on Fri Sep 9 09:15:05 2016
"""
from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre
from app.pe import pe_affichage
import pandas as pd
import numpy as np
@ -71,17 +72,18 @@ class RCSTag(TableTag):
self.nom = self.get_repr()
"""Représentation textuelle du RCS taggué"""
self.formsemestre_terminal = rcrcf.formsemestre_final
"""Le formsemestre terminal"""
# Les données du semestre final
self.formsemestre_terminal: FormSemestre = rcrcf.formsemestre_final
"""Le semestre final"""
self.fid_final: int = rcrcf.formsemestre_final.formsemestre_id
"""Le fid du semestre final"""
# Affichage pour debug
pe_affichage.pe_print(f"-> {self.get_repr(verbose=True)}")
# Les résultats du formsemestre terminal
nt = load_formsemestre_results(self.formsemestre_terminal)
# Les données aggrégés (RCRCF + SxTags
self.rcfs_aggreges = rcrcf.rcfs_aggreges
"""Les RCFs aggrégés"""
self.sxstags = {}
"""Les SxTag associés aux RCF aggrégés"""
try:
@ -91,41 +93,57 @@ class RCSTag(TableTag):
raise ValueError("Semestres SxTag manquants")
# Les étudiants (etuds, états civils & etudis)
self.etuds = nt.etuds
self.add_etuds(nt.etuds)
sxtag_final = self.sxstags[self.rcs_id]
self.etuds = sxtag_final.etuds
"""Les étudiants (extraits du semestre final)"""
self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids)
"""Etudids triés"""
"""Les étudids triés"""
# Les compétences (extraites de tous les Sxtags)
self.association_ues_comp = self.mapping_ue_competences()
"""Association indiquant pour chaque UE , quelle compétence lui correspond"""
pe_affichage.pe_print(f"* Association UEs -> compétences : {self.association_ues_comp}")
self.competences_sorted = self.do_complist()
"""Compétences (triées) extraites de tous les SxTag aggrégés"""
self.acronymes_ues_to_competences = self._do_acronymes_to_competences()
"""L'association acronyme d'UEs -> compétence (extraites des SxTag aggrégés)"""
pe_affichage.pe_print(
f"* Association UEs -> compétences : {self.acronymes_ues_to_competences}"
)
self.competences_sorted = sorted(self.acronymes_ues_to_competences.values())
"""Compétences (triées par nom, extraites des SxTag aggrégés)"""
pe_affichage.pe_print(f"* Compétences : {', '.join(self.competences_sorted)}")
# Les tags
self.tags_sorted = self.do_taglist()
self.tags_sorted = self._do_taglist()
"""Tags extraits de tous les SxTag aggrégés"""
pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}")
# Les moyennes
self.moyennes_tags: dict[str, MoyennesTag] = {}
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
for tag in self.tags_sorted:
# Cube de note
notes_cube, coeffs_cube = self.compute_notes_comps_cube(tag, self.etudids_sorted, self.competences_sorted)
# Calcule des moyennes/coeffs sous forme d'un dataframe"""
moys_competences, coeffs_competences = compute_notes_competences(
notes_cube, coeffs_cube, self.etudids_sorted, self.competences_sorted
# Cube de notes (etudids_sorted x compétences_sorted x sxstags)
notes_df, notes_cube = self.compute_notes_comps_cube(
tag, self.etudids_sorted, self.competences_sorted, self.sxstags
)
# Calcule des moyennes/coeffs sous forme d'un dataframe"""
moys_competences = compute_notes_competences(
notes_cube, self.etudids_sorted, self.competences_sorted
)
# Cube de coeffs pour la moyenne générale,
# traduisant les inscriptions des étudiants aux UEs (etudids_sorted x compétences_sorted x sxstags)
coeffs_df, coeffs_cube = self.compute_coeffs_comps_cube(
tag,
self.etudids_sorted,
self.competences_sorted,
self.sxstags,
)
# Calcule la synthèse des coefficients à prendre en compte pour la moyenne
# générale
matrice_coeffs_moy_gen = compute_coeffs_competences(
coeffs_cube, notes_cube, self.etudids_sorted, self.competences_sorted
)
# Mémorise les moyennes et les coeff associés
self.moyennes_tags[tag] = MoyennesTag(
tag, moys_competences, matrice_coeffs_moy_gen
)
# Les moyennes
self.moyennes_tags[tag] = MoyennesTag(tag, moys_competences,
coeffs_competences)
def __eq__(self, other):
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
@ -139,107 +157,200 @@ class RCSTag(TableTag):
else:
return f"{self.__class__.__name__} ({self.rcs_id})"
def compute_notes_comps_cube(self, tag, etudids_sorted: list[int], competences_sorted: list[str]):
"""Pour un tag donné, construit :
* le cube de notes (etudid x competences x SxTag) nécessaire au calcul des moyennes,
def compute_notes_comps_cube(
self,
tag,
etudids_sorted: list[int],
competences_sorted: list[str],
sxstags: dict[(str, int) : pe_sxtag.SxTag],
):
"""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
* le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions)
appliqué au calcul des différents SxTag
Args:
tag: Le tag visé
etudids_sorted: Les etudis triés
competences_sorted: Les compétences triées
etudids_sorted: Les etudis triés (dim 0)
competences_sorted: Les compétences triées (dim 1)
sxstags: Les SxTag à réunir
"""
# nb_tags = len(self.tags_sorted)
# nb_etudiants = len(self.etuds)
# nb_semestres = len(self.semestres_tags_aggreges)
# Index du cube (etudids -> dim 0, tags -> dim 1)
# etudids = [etud.etudid for etud in self.etuds]
# competences_sorted = self.competences_sorted
sxstags_ids = list(self.sxstags.keys())
notes_dfs = {}
coeffs_dfs = {}
for sxtag_id, sxtag in self.sxstags.items():
for sxtag_id, sxtag in sxstags.items():
# Partant d'un dataframe vierge
notes_df = pd.DataFrame(np.nan, index=etudids_sorted, columns=competences_sorted)
coeffs_df = pd.DataFrame(np.nan, index=etudids_sorted, columns=competences_sorted)
notes_df = pd.DataFrame(
np.nan, index=etudids_sorted, columns=competences_sorted
)
# Charge les notes du semestre tag (copie car changement de nom de colonnes à venir)
moys_tag = sxtag.moyennes_tags[tag]
# Charge les notes et les coeffs du semestre tag
notes = moys_tag.matrice_notes.copy() # avec une copie
coeffs = moys_tag.matrice_coeffs_moy_gen.copy() # les coeffs
# Traduction des UE en compétences
ues_columns_df = notes.columns
comp_associes_aux_ues = [self.association_ues_comp[ue] for ue in ues_columns_df]
notes.columns = comp_associes_aux_ues
coeffs.columns = comp_associes_aux_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)
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 sxstags]
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,
etudids_sorted: list[int],
competences_sorted: list[str],
sxstags: dict[(str, int) : pe_sxtag.SxTag],
):
"""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: Les etudis triés
competences_sorted: Les compétences triées
sxstags: Les SxTag à réunir
"""
coeffs_dfs = {}
for sxtag_id, sxtag in sxstags.items():
# Partant d'un dataframe vierge
coeffs_df = pd.DataFrame(
np.nan, index=etudids_sorted, columns=competences_sorted
)
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
]
# 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
coeffs_dfs[sxtag_id] = coeffs_df
"""Réunit les notes sous forme d'un cube etudids x competences x semestres"""
sxtag_x_etudids_x_comps = [notes_dfs[fid].values for fid in notes_dfs]
notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
"""Réunit les coeffs sous forme d'un cube etudids x competences x semestres"""
sxtag_x_etudids_x_comps = [coeffs_dfs[fid].values for fid in notes_dfs]
sxtag_x_etudids_x_comps = [coeffs_dfs[sxtag_id] for sxtag_id in sxstags]
coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
return notes_etudids_x_comps_x_sxtag, coeffs_etudids_x_comps_x_sxtag
return coeffs_dfs, coeffs_etudids_x_comps_x_sxtag
def do_taglist(self):
"""Synthétise les tags à partir des Sxtags aggrégés
def _do_taglist(self) -> list[str]:
"""Synthétise les tags à partir des Sxtags aggrégés.
Returns:
Une liste de tags triés par ordre alphabétique
Liste de tags triés par ordre alphabétique
"""
tags = []
for frmsem_id in self.sxstags:
tags.extend(self.sxstags[frmsem_id].tags_sorted)
return sorted(set(tags))
def mapping_ue_competences(self):
"""Dictionnaire {ue: competences} extrait des SxTags"""
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.items():
comp = sxtag.competences
dict_competences |= comp
dict_competences |= sxtag.acronymes_ues_to_competences
return dict_competences
def do_complist(self):
"""Synthétise les compétences à partir des Sxtags aggrégés"""
dict_competences = self.mapping_ue_competences()
return sorted(set(dict_competences.values()))
def compute_coeffs_competences(
coeff_cube: np.array,
set_cube: np.array,
etudids_sorted: list,
competences_sorted: list,
):
"""Calcule les coeffs à utiliser pour la moyenne générale (toutes compétences
confondues), en fonction des notes (set_cube) aggrégées.
Args:
coeffs_cube: coeffs impliqués dans la moyenne générale (semestres par semestres)
set_cube: notes moyennes aux modules ndarray
(etuds x UEs|compétences x sxtags), des floats avec des NaN
etudids_sorted: liste des étudiants (dim. 0 du cube)
competences_sorted: list
Returns:
Un DataFrame de coefficients (etudids_sorted x compétences_sorted)
"""
nb_etuds, nb_comps, nb_semestres = set_cube.shape
assert nb_etuds == len(etudids_sorted)
assert nb_comps == len(competences_sorted)
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
# Enlève les NaN du cube de notes pour les entrées manquantes
coeffs_cube_no_nan = np.nan_to_num(coeff_cube, nan=0.0)
# Retire les coefficients associées à 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=etudids_sorted, columns=competences_sorted
)
# Remet à Nan les coeffs à 0
coeffs_df.fillna(np.nan)
return coeffs_df
def compute_notes_competences(
set_cube: np.array, coeff_cube: np.array, etudids_sorted: list, competences_sorted: list
set_cube: np.array,
etudids_sorted: list,
competences_sorted: list,
):
"""Calcule:
* la moyenne par compétences à un tag donné sur plusieurs semestres (partant du set_cube).
* la somme des coeffs à utiliser pour la moyenne générale.
"""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
@ -249,7 +360,6 @@ def compute_notes_competences(
Args:
set_cube: notes moyennes aux modules ndarray
(etuds x UEs|compétences x sxtags), des floats avec des NaN
coeffs_cube: somme des coeffs impliqués dans la moyennes
etudids_sorted: liste des étudiants (dim. 0 du cube)
competences_sorted: list
tags: liste des tags (dim. 1 du cube)
@ -266,13 +376,10 @@ def compute_notes_competences(
# Enlève les NaN du cube de notes pour les entrées manquantes
set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0)
coeffs_cube_no_nan = np.nan_to_num(coeff_cube, 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)
# La somme des coeffs
coeff_tag = np.sum(coeffs_cube_no_nan, axis=2)
# Le dataFrame des notes moyennes
etud_moy_tag_df = pd.DataFrame(
@ -282,7 +389,4 @@ def compute_notes_competences(
)
etud_moy_tag_df.fillna(np.nan)
coeffs_df = pd.DataFrame(coeff_tag, index=etudids_sorted, columns=competences_sorted)
coeffs_df.fillna(np.nan)
return etud_moy_tag_df, coeffs_df
return etud_moy_tag_df

View File

@ -41,7 +41,7 @@ from app import db, ScoValueError
from app import comp
from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre
from app.models import FormSemestre, UniteEns
from app.models.moduleimpls import ModuleImpl
import app.pe.pe_affichage as pe_affichage
import app.pe.pe_etudiant as pe_etudiant
@ -55,7 +55,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
"""
Un ResSemBUTTag représente les résultats des étudiants à un semestre, en donnant
accès aux moyennes par tag.
Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT.
Il s'appuie principalement sur un ResultatsSemestreBUT.
"""
def __init__(self, formsemestre: FormSemestre):
@ -69,51 +69,59 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
# Le nom du res_semestre taggué
self.nom = self.get_repr(verbose=True)
pe_affichage.pe_print(f"--> Résultats de semestre taggués {self.nom}")
pe_affichage.pe_print(f"--> ResultatsSemestreBUT taggués {self.nom}")
# Les étudiants (etuds, états civils & etudis) ajouté
self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids)
"""Les etudids des étudiants du ResultatsSemestreBUT triés"""
# Les UEs (et les dispenses d'UE)
# self.ues
ues_standards = [ue for ue in self.ues if ue.type == sco_codes.UE_STANDARD]
self.ues_standards: list[UniteEns] = [
ue for ue in self.ues if ue.type == sco_codes.UE_STANDARD
]
"""Liste des UEs standards du ResultatsSemestreBUT"""
# Les UEs en fonction des parcours
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
"""Les inscriptions des étudiants aux UEs du parcours"""
# Les acronymes des UEs
self.ues_to_acronymes = {ue.id: ue.acronyme for ue in self.ues_standards}
self.acronymes_sorted = sorted(self.ues_to_acronymes.values())
"""Les acronymes de UE triés par ordre alphabétique"""
# Les compétences associées aux UEs (définies par les acronymes)
self.competences = {}
self.acronymes_ues_to_competences = {}
"""L'association acronyme d'UEs -> compétence"""
for ue in self.ues:
if ue.type == sco_codes.UE_STANDARD:
for ue in self.ues_standards:
assert ue.niveau_competence, ScoValueError(
"Des UEs ne sont pas rattachées à des compétences"
)
nom = ue.niveau_competence.competence.titre
self.competences[ue.acronyme] = nom
# Les acronymes des UEs
self.ues_to_acronymes = {ue.id: ue.acronyme for ue in ues_standards}
self.acronymes_sorted = sorted(self.ues_to_acronymes.values())
"""Les acronymes de UE triés par ordre alphabétique"""
self.acronymes_ues_to_competences[ue.acronyme] = nom
self.competences_sorted = sorted(
list(set(self.acronymes_ues_to_competences.values()))
)
"""Les compétences triées par nom"""
# Les tags personnalisés et auto:
tags_dict = self._get_tags_dict()
self._check_tags(tags_dict)
# Les coefficients pour le calcul de la moyenne générale
self.matrice_coeffs_moy_gen = self.ues_inscr_parcours_df * [
ue.ects for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé
]
# Les coefficients pour le calcul de la moyenne générale, donnés par
# acronymes d'UE
self.matrice_coeffs_moy_gen = self._get_matrice_coeffs(
self.ues_inscr_parcours_df, self.ues_standards
)
"""DataFrame indiquant les coeffs des UEs par ordre alphabétique d'acronyme"""
# Les capitalisations (mask etuids x acronyme_ue valant True si capitalisée, False sinon)
self.capitalisations = self._get_capitalisations(ues_standards)
self.capitalisations = self._get_capitalisations(self.ues_standards)
"""DataFrame indiquant les UEs capitalisables d'un étudiant (etudids x )"""
# Calcul des moyennes & les classements de chaque étudiant à chaque tag
self.moyennes_tags = {}
"""Les moyennes par tags (personnalisés ou 'but')"""
for tag in tags_dict["personnalises"]:
# pe_affichage.pe_print(f" -> Traitement du tag {tag}")
infos_tag = tags_dict["personnalises"][tag]
@ -123,49 +131,76 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
)
# Ajoute les moyennes par UEs + la moyenne générale (but)
df_ues = pd.DataFrame(
{ue.id: self.etud_moy_ue[ue.id] for ue in ues_standards},
index=self.etudids,
)
# Transforme les UEs en acronyme
colonnes = df_ues.columns
acronymes = [self.ues_to_acronymes[col] for col in colonnes]
df_ues.columns = acronymes
moy_gen = self.compute_moy_gen()
self.moyennes_tags["but"] = MoyennesTag(
"but", df_ues, self.matrice_coeffs_moy_gen # , moy_gen_but
"but", moy_gen, self.matrice_coeffs_moy_gen # , moy_gen_but
)
self.tags_sorted = self.get_all_tags()
"""Tags (personnalisés+compétences) par ordre alphabétique"""
def get_repr(self, verbose=False):
"""Nom affiché pour le semestre taggué"""
if verbose:
return f"{self.formsemestre} (#{self.formsemestre.formsemestre_id})"
def get_repr(self, verbose=False) -> str:
"""Nom affiché pour le semestre taggué, de la forme (par ex.):
* S1#69 si verbose est False
* S1 FI 2023 si verbose est True
"""
if not verbose:
return f"{self.formsemestre}#{self.formsemestre.formsemestre_id}"
else:
return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
def _get_capitalisations(self, ues_hors_sport) -> pd.DataFrame:
"""Renvoie un dataFrame résumant les UEs capitalisables par les
étudiants, d'après les décisions de jury
def _get_matrice_coeffs(
self, ues_inscr_parcours_df: pd.DataFrame, ues_standards: list[UniteEns]
) -> pd.DataFrame:
"""Renvoie un dataFrame donnant les coefficients à appliquer aux UEs
dans le calcul de la moyenne générale (toutes UEs confondues).
Prend en compte l'inscription des étudiants aux UEs en fonction de leur parcours
(cf. ues_inscr_parcours_df).
Args:
ues_hors_sport: Liste des UEs autres que le sport
ues_inscr_parcours_df: Les inscriptions des étudiants aux UEs
ues_standards: Les UEs standards à prendre en compte
Returns:
Un dataFrame etudids x acronymes_UEs avec les coeffs des UEs
"""
capitalisations = pd.DataFrame(False, index=self.etudids_sorted, columns=self.acronymes_sorted)
matrice_coeffs_moy_gen = ues_inscr_parcours_df * [
ue.ects for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé
]
matrice_coeffs_moy_gen.columns = [
self.ues_to_acronymes[ue.id] for ue in ues_standards
]
# Tri par etudids (dim 0) et par acronymes (dim 1)
matrice_coeffs_moy_gen = matrice_coeffs_moy_gen.sort_index()
matrice_coeffs_moy_gen = matrice_coeffs_moy_gen.sort_index(axis=1)
return matrice_coeffs_moy_gen
def _get_capitalisations(self, ues_standards) -> pd.DataFrame:
"""Renvoie un dataFrame résumant les UEs capitalisables par les
étudiants, d'après les décisions de jury (sous réserve qu'elles existent).
Args:
ues_standards: Liste des UEs standards (notamment autres que le sport)
Returns:
Un dataFrame etudids x acronymes_UEs dont les valeurs sont ``True`` si l'UE
est capitalisable, ``False`` sinon
"""
capitalisations = pd.DataFrame(
False, index=self.etudids_sorted, columns=self.acronymes_sorted
)
self.get_formsemestre_validations() # charge les validations
res_jury = self.validations
if res_jury:
for etud in self.etuds:
etudid = etud.etudid
decisions = res_jury.decisions_jury_ues.get(etudid, {})
for ue in ues_hors_sport:
for ue in ues_standards:
if ue.id in decisions and decisions[ue.id]["code"] == sco_codes.ADM:
capitalisations.loc[etudid, ue.acronyme] = True
# pe_affichage.pe_print(
# f" ⚠ Capitalisation de {ue.acronyme} pour {etud.etat_civil}"
# )
# Tri par etudis et par accronyme d'UE
capitalisations = capitalisations.sort_index()
capitalisations = capitalisations.sort_index(axis=1)
return capitalisations
def compute_moy_ues_tag(self, info_tag: dict[int, dict]) -> pd.DataFrame:
@ -208,14 +243,38 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
block=self.formsemestre.block_moyennes,
)
# Ne conserve que les UEs standards
colonnes = [ue.id for ue in self.ues_standards]
moyennes_ues_tag = moyennes_ues_tag[colonnes]
# Transforme les UEs en acronyme
colonnes = moyennes_ues_tag.columns
ue_to_acro = {ue.id: ue.acronyme for ue in self.ues}
acronymes = [ue_to_acro[col] for col in colonnes]
acronymes = [self.ues_to_acronymes[ue.id] for ue in self.ues_standards]
moyennes_ues_tag.columns = acronymes
# Tri par etudids et par ordre alphabétique d'acronyme
moyennes_ues_tag = moyennes_ues_tag.sort_index()
moyennes_ues_tag = moyennes_ues_tag.sort_index(axis=1)
return moyennes_ues_tag
def compute_moy_gen(self):
"""Récupère les moyennes des UEs pour le calcul de la moyenne générale,
en associant à chaque UE.id son acronyme (toutes UEs confondues)
"""
df_ues = pd.DataFrame(
{ue.id: self.etud_moy_ue[ue.id] for ue in self.ues_standards},
index=self.etudids,
)
# Transforme les UEs en acronyme
colonnes = df_ues.columns
acronymes = [self.ues_to_acronymes[col] for col in colonnes]
df_ues.columns = acronymes
# Tri par ordre aphabétique de colonnes
df_ues.sort_index(axis=1)
return df_ues
def _get_tags_dict(self):
"""Renvoie les tags personnalisés (déduits des modules du semestre)
et les tags automatiques ('but'), et toutes leurs informations,

View File

@ -54,20 +54,24 @@ class SxTag(TableTag):
ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag],
):
"""Calcule les moyennes/classements par tag d'un semestre de type 'Sx'
(par ex. 'S1', 'S2', ...) avec une orientation par UE :
(par ex. 'S1', 'S2', ...) représentés par acronyme d'UE.
* pour les étudiants non redoublants, ce sont les moyennes/classements
Il représente :
* pour les étudiants *non redoublants* : moyennes/classements
du semestre suivi
* pour les étudiants redoublants, c'est une fusion des moyennes/classements
dans les (2) 'Sx' qu'il a suivi
* pour les étudiants *redoublants* : une fusion des moyennes/classements
dans les (2) 'Sx' qu'il a suivi, en exploitant les informations de capitalisation :
meilleure moyenne entre l'UE capitalisée et l'UE refaite (la notion de meilleure
s'appliquant à la moyenne d'UE)
Un SxTag peut donc regrouper plusieurs semestres.
Un SxTag (regroupant potentiellement plusieurs semestres) est identifié
par un tuple ``(Sx, fid)`` :
Un SxTag est identifié par un tuple (x, fid) x est le numéro (semestre_id)
du semestre et fid le formsemestre_id du semestre final (le plus récent) du
regrouprement.
* ``x`` est le rang (semestre_id) du semestre
* ``fid`` le formsemestre_id du semestre final (le plus récent) du regroupement.
Les **tags**, les **UE** et les inscriptions aux UEs (pour les etudiants)
Les **tags**, les **UE** et les inscriptions aux UEs (pour les étudiants)
considérés sont uniquement ceux du semestre final.
Args:
@ -85,6 +89,7 @@ class SxTag(TableTag):
self.rcf = rcf
"""Le RCF sur lequel il s'appuie"""
assert rcf.rcs_id == sxtag_id, "Problème de correspondance SxTag/RCF"
# Les resultats des semestres taggués à prendre en compte dans le RCF
self.ressembuttags = {fid: ressembuttags[fid] for fid in rcf.semestres_aggreges}
@ -95,10 +100,9 @@ class SxTag(TableTag):
self.ressembuttag_final = ressembuttags[self.fid_final]
"""Le ResSemBUTTag final"""
self.etuds = ressembuttags[self.fid_final].etuds
"""Les étudiants du ReSemBUTTag final"""
# Ajout les etudids et les états civils
# Ajoute les etudids et les états civils
self.etuds = self.ressembuttag_final.etuds
"""Les étudiants (extraits du ReSemBUTTag final)"""
self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids)
"""Les etudids triés"""
@ -108,89 +112,94 @@ class SxTag(TableTag):
# Les tags
self.tags_sorted = self.ressembuttag_final.tags_sorted
"""Tags (extraits uniquement du semestre final)"""
"""Tags (extraits du ReSemBUTTag 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 UE données par leur acronyme
self.acronymes_sorted = self.ressembuttag_final.acronymes_sorted
"""Les acronymes des UEs (extraits du ResSemBUTTag final)"""
# L'association UE-compétences extraites du dernier semestre
self.competences = self.ressembuttag_final.competences
self.acronymes_ues_to_competences = (
self.ressembuttag_final.acronymes_ues_to_competences
)
"""L'association acronyme d'UEs -> compétence"""
self.competences_sorted = sorted(self.acronymes_ues_to_competences.values())
"""Les compétences triées par nom"""
# 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.ues_inscr_parcours_df = self.ressembuttag_final.ues_inscr_parcours_df
self.ues_inscr_parcours_df.sort_index()
# Les coeffs pour la moyenne générale
self.matrice_coeffs_moy_gen = self.ressembuttag_final.moyennes_tags[
"but"
].matrice_coeffs_moy_gen
self.matrice_coeffs_moy_gen.sort_index() # Trie les coeff par etudids
# Les moyennes par tag
self.moyennes_tags: dict[str, pd.DataFrame] = {}
"""Les notes aux UEs dans différents tags"""
# Les coeffs pour la moyenne générale (traduisant également l'inscription
# des étudiants aux UEs) (etudids_sorted x acronymes_ues_sorted)
self.matrice_coeffs_moy_gen = self.ressembuttag_final.matrice_coeffs_moy_gen
# Masque des inscriptions et des capitalisations
self.masque_df, masque_cube = compute_masques_ues_cube(
self.masque_df = None
"""Le DataFrame traduisant les capitalisations des différents semestres"""
self.masque_df, masque_cube = compute_masques_capitalisation_cube(
self.etudids_sorted,
self.acronymes_ues_sorted,
self.acronymes_sorted,
self.ressembuttags,
self.fid_final,
)
self._aff_capitalisations()
# Les moyennes par tag
self.moyennes_tags: dict[str, pd.DataFrame] = {}
"""Moyennes aux UEs (identifiées par leur acronyme) des différents tags"""
for tag in self.tags_sorted:
# Y-a-t-il des notes ?
if not self.has_notes(tag):
pe_affichage.pe_print(f"> MoyTag 🏷{tag} actuellement sans ◯ notes")
matrice_moys_ues = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.acronymes_ues_sorted)
matrice_moys_ues = pd.DataFrame(
np.nan, index=self.etudids_sorted, columns=self.acronymes_sorted
)
else:
# Cube de note etudids x UEs
notes_df, notes_cube = compute_notes_ues_cube(
tag,
self.etudids_sorted,
self.acronymes_ues_sorted,
self.acronymes_sorted,
self.ressembuttags,
)
# self.ues_inscr_parcours = ~np.isnan(self.matrice_coeffs.to_numpy())
# inscr_mask = self.ues_inscr_parcours
# Masque des inscriptions aux UEs (extraits de la matrice de coefficients)
inscr_mask: np.array = ~np.isnan(self.matrice_coeffs_moy_gen.to_numpy())
# Calcule des moyennes sous forme d'un dataframe
inscr_mask = ~np.isnan(self.ues_inscr_parcours_df.to_numpy())
# Matrice des moyennes
matrice_moys_ues: pd.DataFrame = compute_notes_ues(
notes_cube,
masque_cube,
self.etudids_sorted,
self.acronymes_ues_sorted,
self.acronymes_sorted,
inscr_mask,
)
# Affichage de debug
self.__aff_profil_coeff_ects(tag)
# Mémorise les infos pour la moyennes au tag
self.moyennes_tags[tag] = MoyennesTag(
tag, matrice_moys_ues, self.matrice_coeffs_moy_gen
)
def __aff_profil_coeff_ects(self, tag):
"""Extrait de la matrice des coeffs, les différents types d'inscription
et de coefficients (appelés profil) des étudiants et les affiche
(pour debug)
"""
# Les profils d'ects (pour debug)
profils_ects = []
for i in self.matrice_coeffs_moy_gen.index:
val = tuple(self.matrice_coeffs_moy_gen.loc[i].fillna("x"))
if tuple(val) not in profils_ects:
profils_ects.append(tuple(val))
# L'affichage
ues = ", ".join(self.acronymes_sorted)
pe_affichage.pe_print(
f"> MoyTag 🏷{tag} avec "
+ f"ues={self.acronymes_ues_sorted} "
+ f"ects={profils_ects}"
f"> MoyTag 🏷{tag} avec " + f"ues={ues} " + f"inscr/ects={profils_ects}"
)
# Les moyennes au tag
self.moyennes_tags[tag] = MoyennesTag(
tag, matrice_moys_ues, self.matrice_coeffs_moy_gen
)
def has_notes(self, tag):
"""Détermine si le SxTag, pour un tag donné, est en cours d'évaluation.
Si oui, n'a pas (encore) de notes dans le resformsemestre final.
@ -229,7 +238,7 @@ class SxTag(TableTag):
cap = []
for frmsem_id in self.ressembuttags:
if frmsem_id != self.fid_final:
for accr in self.acronymes_ues_sorted:
for accr in self.acronymes_sorted:
if self.masque_df[frmsem_id].loc[etud.etudid, accr] > 0.0:
cap += [accr]
if cap:
@ -239,7 +248,7 @@ class SxTag(TableTag):
def compute_notes_ues_cube(
tag, etudids_sorted, acronymes_ues_sorted, ressembuttags
tag, etudids_sorted, acronymes_sorted, ressembuttags
) -> (pd.DataFrame, np.array):
"""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.
@ -247,7 +256,7 @@ def compute_notes_ues_cube(
Args:
etudids_sorted: La liste des etudids triés par ordre croissant (dim 0)
acronymes_ues_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1)
acronymes_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1)
ressembuttags: Le dictionnaire des résultats de semestres BUT (tous tags confondus)
"""
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
@ -259,7 +268,7 @@ def compute_notes_ues_cube(
for frmsem_id in semestres_id:
# Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids_sorted, columns=acronymes_ues_sorted)
df = pd.DataFrame(np.nan, index=etudids_sorted, columns=acronymes_sorted)
# Charge les notes du semestre tag
sem_tag = ressembuttags[frmsem_id]
@ -289,23 +298,27 @@ def compute_notes_ues_cube(
return dfs, etudids_x_ues_x_semestres
def compute_masques_ues_cube(
def compute_masques_capitalisation_cube(
etudids_sorted: list[int],
acronymes_ues_sorted: list[str],
acronymes_sorted: list[str],
ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag],
formsemestre_id_final: int,
) -> (pd.DataFrame, np.array):
"""Construit le cube traduisant le masque des UEs à prendre en compte dans le calcul
des moyennes, en utilisant le df capitalisations de chaque ResSemBUTTag
"""Construit le cube traduisant les masques des UEs à prendre en compte dans le calcul
des moyennes, en utilisant le dataFrame de capitalisations de chaque ResSemBUTTag
Ce masque contient : 1 si la note doit être prise en compte ; 0 sinon
Ces masques contiennent : 1 si la note doit être prise en compte, 0 sinon
Le masque des UEs à prendre en compte correspondant au semestre final (identifié par
son formsemestre_id_final) est systématiquement à 1 (puisque les résultats
de ce semestre doivent systématiquement
être pris en compte notamment pour les étudiants non redoublant).
Args:
etudids_sorted: La liste des etudids triés par ordre croissant (dim 0)
acronymes_ues_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1)
# ues_inscr_parcours_df: Le dataFrame des inscriptions au UE en fonction du parcours
acronymes_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1)
ressembuttags: Le dictionnaire des résultats de semestres BUT (tous tags confondus)
formsemestre_id_final: L'identifiant du formsemestre_id_final (dont il faut forcément prendre en compte les coeffs)
formsemestre_id_final: L'identifiant du formsemestre_id_final
"""
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
# etudids_sorted = etudids_sorted
@ -317,15 +330,16 @@ def compute_masques_ues_cube(
for frmsem_id in semestres_id:
# Partant d'un dataframe contenant des 1.0
if frmsem_id == formsemestre_id_final:
df = pd.DataFrame(1.0, index=etudids_sorted, columns=acronymes_ues_sorted)
df = pd.DataFrame(1.0, index=etudids_sorted, columns=acronymes_sorted)
else: # semestres redoublés
df = pd.DataFrame(0.0, index=etudids_sorted, columns=acronymes_ues_sorted)
df = pd.DataFrame(0.0, index=etudids_sorted, columns=acronymes_sorted)
# Traitement des capitalisations
# Traitement des capitalisations : remplace les infos de capitalisations par les coeff 1 ou 0
capitalisations = ressembuttags[frmsem_id].capitalisations
capitalisations = capitalisations.replace(True, 1.0).replace(False, 0.0)
# Met à 0 les coeffs des UEs non capitalisées : 1.0*False => 0.0
# Met à 0 les coeffs des UEs non capitalisées pour les étudiants
# inscrits dans les 2 semestres: 1.0*False => 0.0
etudids_communs, acronymes_communs = pe_comp.find_index_and_columns_communs(
df, capitalisations
)
@ -347,7 +361,7 @@ def compute_notes_ues(
set_cube: np.array,
masque_cube: np.array,
etudids_sorted: list,
acronymes_ues_sorted: list,
acronymes_sorted: list,
inscr_mask: np.array,
):
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
@ -359,7 +373,7 @@ def compute_notes_ues(
masque_cube: masque indiquant si la note doit être prise en compte ndarray
(semestre_ids x etudids x UEs), des 1.0 ou des 0.0
etudids_sorted: liste des étudiants (dim. 0 du cube) trié par etudid
acronymes_ues_sorted: liste des acronymes des ues (dim. 1 du cube) trié par acronyme
acronymes_sorted: liste des acronymes des ues (dim. 1 du cube) trié par acronyme
inscr_mask: masque etudids x UE traduisant les inscriptions des
étudiants aux UE (du semestre terminal)
Returns:
@ -369,7 +383,7 @@ def compute_notes_ues(
nb_etuds, nb_ues, nb_semestres = set_cube.shape
nb_etuds_mask, nb_ues_mask = inscr_mask.shape
assert nb_etuds == len(etudids_sorted)
assert nb_ues == len(acronymes_ues_sorted)
assert nb_ues == len(acronymes_sorted)
assert nb_etuds == nb_etuds_mask
assert nb_ues == nb_ues_mask
@ -397,7 +411,7 @@ def compute_notes_ues(
etud_moy_tag_df = pd.DataFrame(
etud_moy,
index=etudids_sorted, # les etudids
columns=acronymes_ues_sorted, # les tags
columns=acronymes_sorted, # les acronymes d'UEs
)
etud_moy_tag_df.fillna(np.nan)

View File

@ -105,7 +105,7 @@ def associe_ues_et_parcours(formation: Formation, formation_infos: dict):
# Niveaux compétences:
if ue_infos.get("competence"):
competence = referentiel_competence.competences.filter_by(
competence = referentiel_competence.acronymes_ues_to_competences.filter_by(
titre=ue_infos["competence"]
).first()
assert competence is not None # La compétence de titre indiqué doit exister