1
0
forked from ScoDoc/ScoDoc

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

View File

@ -3,9 +3,7 @@ import pandas as pd
from app import comp from app import comp
from app.comp.moy_sem import comp_ranks_series from app.comp.moy_sem import comp_ranks_series
from app.models import UniteEns
from app.pe import pe_affichage from app.pe import pe_affichage
from app.scodoc.codes_cursus import UE_SPORT
class Moyenne: class Moyenne:
@ -24,12 +22,12 @@ class Moyenne:
"""Classe centralisant la synthèse des moyennes/classements d'une série """Classe centralisant la synthèse des moyennes/classements d'une série
de notes : de notes :
* des "notes": la Serie pandas des notes (float), * des "notes" : la Serie pandas des notes (float),
* des "classements": la Serie pandas des classements (float), * des "classements" : la Serie pandas des classements (float),
* des "min": la note minimum, * des "min" : la note minimum,
* des "max": la note maximum, * des "max" : la note maximum,
* des "moy": la moyenne, * des "moy" : la moyenne,
* des "nb_inscrits": le nombre d'étudiants ayant une note, * des "nb_inscrits" : le nombre d'étudiants ayant une note,
""" """
self.notes = notes self.notes = notes
"""Les notes""" """Les notes"""
@ -171,7 +169,7 @@ class MoyennesTag:
tag: Un tag tag: Un tag
matrice_notes: Les moyennes (etudid x acronymes_ues ou etudid x compétences) aux différentes UEs ou compétences 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 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 self.tag = tag
"""Le tag associé aux moyennes""" """Le tag associé aux moyennes"""
@ -207,7 +205,7 @@ class MoyennesTag:
ont des notes ont des notes
Returns: Returns:
True si a des notes, False sinon True si la moytag a des notes, False sinon
""" """
notes = self.matrice_notes notes = self.matrice_notes
nbre_nan = notes.isna().sum().sum() nbre_nan = notes.isna().sum().sum()

View File

@ -152,7 +152,8 @@ class RCSsJuryPE:
# Ajout du RCRCF # Ajout du RCRCF
if rcf_id not in self.rcrcfs: 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] rcrcf = self.rcrcfs[rcf_id]
# Ajout des RCFs au RCRCF # 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.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre
from app.pe import pe_affichage from app.pe import pe_affichage
import pandas as pd import pandas as pd
import numpy as np import numpy as np
@ -49,7 +50,7 @@ from app.pe.pe_moytag import MoyennesTag
class RCSTag(TableTag): class RCSTag(TableTag):
def __init__(self, rcrcf: pe_rcs.RCS, sxstags: dict[(str, int): pe_sxtag.SxTag]): def __init__(self, rcrcf: pe_rcs.RCS, sxstags: dict[(str, int) : pe_sxtag.SxTag]):
"""Calcule les moyennes par tag (orientées compétences) """Calcule les moyennes par tag (orientées compétences)
d'un regroupement de SxTag d'un regroupement de SxTag
(RCRCF), pour extraire les classements par tag pour un (RCRCF), pour extraire les classements par tag pour un
@ -71,17 +72,18 @@ class RCSTag(TableTag):
self.nom = self.get_repr() self.nom = self.get_repr()
"""Représentation textuelle du RCS taggué""" """Représentation textuelle du RCS taggué"""
self.formsemestre_terminal = rcrcf.formsemestre_final # Les données du semestre final
"""Le formsemestre terminal""" 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)}") pe_affichage.pe_print(f"-> {self.get_repr(verbose=True)}")
# Les résultats du formsemestre terminal # Les données aggrégés (RCRCF + SxTags
nt = load_formsemestre_results(self.formsemestre_terminal)
self.rcfs_aggreges = rcrcf.rcfs_aggreges self.rcfs_aggreges = rcrcf.rcfs_aggreges
"""Les RCFs aggrégés""" """Les RCFs aggrégés"""
self.sxstags = {} self.sxstags = {}
"""Les SxTag associés aux RCF aggrégés""" """Les SxTag associés aux RCF aggrégés"""
try: try:
@ -91,41 +93,57 @@ class RCSTag(TableTag):
raise ValueError("Semestres SxTag manquants") raise ValueError("Semestres SxTag manquants")
# Les étudiants (etuds, états civils & etudis) # Les étudiants (etuds, états civils & etudis)
self.etuds = nt.etuds sxtag_final = self.sxstags[self.rcs_id]
self.add_etuds(nt.etuds) self.etuds = sxtag_final.etuds
"""Les étudiants (extraits du semestre final)"""
self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids) self.etudids_sorted = sorted(self.etudids)
"""Etudids triés""" """Les étudids triés"""
# Les compétences (extraites de tous les Sxtags) # Les compétences (extraites de tous les Sxtags)
self.association_ues_comp = self.mapping_ue_competences() self.acronymes_ues_to_competences = self._do_acronymes_to_competences()
"""Association indiquant pour chaque UE , quelle compétence lui correspond""" """L'association acronyme d'UEs -> compétence (extraites des SxTag aggrégés)"""
pe_affichage.pe_print(f"* Association UEs -> compétences : {self.association_ues_comp}") pe_affichage.pe_print(
f"* Association UEs -> compétences : {self.acronymes_ues_to_competences}"
self.competences_sorted = self.do_complist() )
"""Compétences (triées) extraites de tous les SxTag aggrégés""" 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)}") pe_affichage.pe_print(f"* Compétences : {', '.join(self.competences_sorted)}")
# Les tags # Les tags
self.tags_sorted = self.do_taglist() self.tags_sorted = self._do_taglist()
"""Tags extraits de tous les SxTag aggrégés""" """Tags extraits de tous les SxTag aggrégés"""
pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}") pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}")
# Les moyennes # Les moyennes
self.moyennes_tags: dict[str, MoyennesTag] = {} self.moyennes_tags: dict[str, MoyennesTag] = {}
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)""" """Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
for tag in self.tags_sorted: for tag in self.tags_sorted:
# Cube de note # Cube de notes (etudids_sorted x compétences_sorted x sxstags)
notes_cube, coeffs_cube = self.compute_notes_comps_cube(tag, self.etudids_sorted, self.competences_sorted) 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, coeffs_competences = compute_notes_competences( # Calcule des moyennes/coeffs sous forme d'un dataframe"""
notes_cube, coeffs_cube, self.etudids_sorted, self.competences_sorted 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): def __eq__(self, other):
"""Egalité de 2 RCS taggués sur la base de leur identifiant""" """Egalité de 2 RCS taggués sur la base de leur identifiant"""
@ -139,107 +157,200 @@ class RCSTag(TableTag):
else: else:
return f"{self.__class__.__name__} ({self.rcs_id})" return f"{self.__class__.__name__} ({self.rcs_id})"
def compute_notes_comps_cube(self, tag, etudids_sorted: list[int], competences_sorted: list[str]): def compute_notes_comps_cube(
"""Pour un tag donné, construit : self,
* le cube de notes (etudid x competences x SxTag) nécessaire au calcul des moyennes, tag,
en remplaçant les données d'UE (obtenus du SxTag) par les compétences etudids_sorted: list[int],
* le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions) competences_sorted: list[str],
appliqué au calcul des différents SxTag 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
Args: Args:
tag: Le tag visé tag: Le tag visé
etudids_sorted: Les etudis triés etudids_sorted: Les etudis triés (dim 0)
competences_sorted: Les compétences triées 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 = {} notes_dfs = {}
coeffs_dfs = {}
for sxtag_id, sxtag in self.sxstags.items(): for sxtag_id, sxtag in sxstags.items():
# Partant d'un dataframe vierge # Partant d'un dataframe vierge
notes_df = pd.DataFrame(np.nan, index=etudids_sorted, columns=competences_sorted) notes_df = pd.DataFrame(
coeffs_df = pd.DataFrame(np.nan, index=etudids_sorted, columns=competences_sorted) 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] moys_tag = sxtag.moyennes_tags[tag]
# Charge les notes et les coeffs du semestre tag
notes = moys_tag.matrice_notes.copy() # avec une copie 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 # Traduction des acronymes d'UE en compétences
ues_columns_df = notes.columns acronymes_ues_columns = notes.columns
comp_associes_aux_ues = [self.association_ues_comp[ue] for ue in ues_columns_df] acronymes_to_comps = [
notes.columns = comp_associes_aux_ues self.acronymes_ues_to_competences[acro]
coeffs.columns = comp_associes_aux_ues for acro in acronymes_ues_columns
]
notes.columns = acronymes_to_comps
# Les étudiants et les compétences communes # 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 # Recopie des notes et des coeffs
notes_df.loc[etudids_communs, comp_communes] = notes.loc[ notes_df.loc[etudids_communs, comp_communes] = notes.loc[
etudids_communs, comp_communes 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[ coeffs_df.loc[etudids_communs, comp_communes] = coeffs.loc[
etudids_communs, comp_communes 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 # Stocke les dfs
notes_dfs[sxtag_id] = notes_df
coeffs_dfs[sxtag_id] = coeffs_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""" """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) 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): def _do_taglist(self) -> list[str]:
"""Synthétise les tags à partir des Sxtags aggrégés """Synthétise les tags à partir des Sxtags aggrégés.
Returns: Returns:
Une liste de tags triés par ordre alphabétique Liste de tags triés par ordre alphabétique
""" """
tags = [] tags = []
for frmsem_id in self.sxstags: for frmsem_id in self.sxstags:
tags.extend(self.sxstags[frmsem_id].tags_sorted) tags.extend(self.sxstags[frmsem_id].tags_sorted)
return sorted(set(tags)) return sorted(set(tags))
def mapping_ue_competences(self): def _do_acronymes_to_competences(self) -> dict[str:str]:
"""Dictionnaire {ue: competences} extrait des SxTags""" """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 = {} dict_competences = {}
for sxtag_id, sxtag in self.sxstags.items(): for sxtag_id, sxtag in self.sxstags.items():
comp = sxtag.competences dict_competences |= sxtag.acronymes_ues_to_competences
dict_competences |= comp
return dict_competences return dict_competences
def do_complist(self):
"""Synthétise les compétences à partir des Sxtags aggrégés""" def compute_coeffs_competences(
dict_competences = self.mapping_ue_competences() coeff_cube: np.array,
return sorted(set(dict_competences.values())) 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( 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: """Calcule la moyenne par compétences (à un tag donné) sur plusieurs semestres (partant du set_cube).
* 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.
La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles
@ -249,7 +360,6 @@ def compute_notes_competences(
Args: Args:
set_cube: notes moyennes aux modules ndarray set_cube: notes moyennes aux modules ndarray
(etuds x UEs|compétences x sxtags), des floats avec des NaN (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) etudids_sorted: liste des étudiants (dim. 0 du cube)
competences_sorted: list competences_sorted: list
tags: liste des tags (dim. 1 du cube) 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 # 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) 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 # Les moyennes par tag
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) 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) 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 # Le dataFrame des notes moyennes
etud_moy_tag_df = pd.DataFrame( etud_moy_tag_df = pd.DataFrame(
@ -282,7 +389,4 @@ def compute_notes_competences(
) )
etud_moy_tag_df.fillna(np.nan) etud_moy_tag_df.fillna(np.nan)
coeffs_df = pd.DataFrame(coeff_tag, index=etudids_sorted, columns=competences_sorted) return etud_moy_tag_df
coeffs_df.fillna(np.nan)
return etud_moy_tag_df, coeffs_df

View File

@ -41,7 +41,7 @@ from app import db, ScoValueError
from app import comp from app import comp
from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_sem import load_formsemestre_results 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 from app.models.moduleimpls import ModuleImpl
import app.pe.pe_affichage as pe_affichage import app.pe.pe_affichage as pe_affichage
import app.pe.pe_etudiant as pe_etudiant 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 Un ResSemBUTTag représente les résultats des étudiants à un semestre, en donnant
accès aux moyennes par tag. 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): def __init__(self, formsemestre: FormSemestre):
@ -69,51 +69,59 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
# Le nom du res_semestre taggué # Le nom du res_semestre taggué
self.nom = self.get_repr(verbose=True) 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é # Les étudiants (etuds, états civils & etudis) ajouté
self.add_etuds(self.etuds) self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids) self.etudids_sorted = sorted(self.etudids)
"""Les etudids des étudiants du ResultatsSemestreBUT triés"""
# Les UEs (et les dispenses d'UE) # Les UEs (et les dispenses d'UE)
# self.ues self.ues_standards: list[UniteEns] = [
ues_standards = [ue for ue in self.ues if ue.type == sco_codes.UE_STANDARD] 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 # Les UEs en fonction des parcours
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours() self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
"""Les inscriptions des étudiants aux UEs du parcours"""
# Les compétences associées aux UEs (définies par les acronymes)
self.competences = {}
"""L'association acronyme d'UEs -> compétence"""
for ue in self.ues:
if ue.type == sco_codes.UE_STANDARD:
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 # Les acronymes des UEs
self.ues_to_acronymes = {ue.id: ue.acronyme for ue in ues_standards} self.ues_to_acronymes = {ue.id: ue.acronyme for ue in self.ues_standards}
self.acronymes_sorted = sorted(self.ues_to_acronymes.values()) self.acronymes_sorted = sorted(self.ues_to_acronymes.values())
"""Les acronymes de UE triés par ordre alphabétique""" """Les acronymes de UE triés par ordre alphabétique"""
# Les compétences associées aux UEs (définies par les acronymes)
self.acronymes_ues_to_competences = {}
"""L'association acronyme d'UEs -> compétence"""
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.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: # Les tags personnalisés et auto:
tags_dict = self._get_tags_dict() tags_dict = self._get_tags_dict()
self._check_tags(tags_dict) self._check_tags(tags_dict)
# Les coefficients pour le calcul de la moyenne générale # Les coefficients pour le calcul de la moyenne générale, donnés par
self.matrice_coeffs_moy_gen = self.ues_inscr_parcours_df * [ # acronymes d'UE
ue.ects for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé 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) # 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 # Calcul des moyennes & les classements de chaque étudiant à chaque tag
self.moyennes_tags = {} self.moyennes_tags = {}
"""Les moyennes par tags (personnalisés ou 'but')"""
for tag in tags_dict["personnalises"]: for tag in tags_dict["personnalises"]:
# pe_affichage.pe_print(f" -> Traitement du tag {tag}") # pe_affichage.pe_print(f" -> Traitement du tag {tag}")
infos_tag = tags_dict["personnalises"][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) # Ajoute les moyennes par UEs + la moyenne générale (but)
df_ues = pd.DataFrame( moy_gen = self.compute_moy_gen()
{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
self.moyennes_tags["but"] = MoyennesTag( 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() self.tags_sorted = self.get_all_tags()
"""Tags (personnalisés+compétences) par ordre alphabétique""" """Tags (personnalisés+compétences) par ordre alphabétique"""
def get_repr(self, verbose=False): def get_repr(self, verbose=False) -> str:
"""Nom affiché pour le semestre taggué""" """Nom affiché pour le semestre taggué, de la forme (par ex.):
if verbose:
return f"{self.formsemestre} (#{self.formsemestre.formsemestre_id})" * 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: else:
return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True) return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
def _get_capitalisations(self, ues_hors_sport) -> pd.DataFrame: def _get_matrice_coeffs(
"""Renvoie un dataFrame résumant les UEs capitalisables par les self, ues_inscr_parcours_df: pd.DataFrame, ues_standards: list[UniteEns]
étudiants, d'après les décisions de jury ) -> 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: 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 self.get_formsemestre_validations() # charge les validations
res_jury = self.validations res_jury = self.validations
if res_jury: if res_jury:
for etud in self.etuds: for etud in self.etuds:
etudid = etud.etudid etudid = etud.etudid
decisions = res_jury.decisions_jury_ues.get(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: if ue.id in decisions and decisions[ue.id]["code"] == sco_codes.ADM:
capitalisations.loc[etudid, ue.acronyme] = True capitalisations.loc[etudid, ue.acronyme] = True
# pe_affichage.pe_print( # Tri par etudis et par accronyme d'UE
# f" ⚠ Capitalisation de {ue.acronyme} pour {etud.etat_civil}" capitalisations = capitalisations.sort_index()
# ) capitalisations = capitalisations.sort_index(axis=1)
return capitalisations return capitalisations
def compute_moy_ues_tag(self, info_tag: dict[int, dict]) -> pd.DataFrame: 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, 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 # Transforme les UEs en acronyme
colonnes = moyennes_ues_tag.columns acronymes = [self.ues_to_acronymes[ue.id] for ue in self.ues_standards]
ue_to_acro = {ue.id: ue.acronyme for ue in self.ues}
acronymes = [ue_to_acro[col] for col in colonnes]
moyennes_ues_tag.columns = acronymes 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 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): def _get_tags_dict(self):
"""Renvoie les tags personnalisés (déduits des modules du semestre) """Renvoie les tags personnalisés (déduits des modules du semestre)
et les tags automatiques ('but'), et toutes leurs informations, et les tags automatiques ('but'), et toutes leurs informations,

View File

@ -54,20 +54,24 @@ class SxTag(TableTag):
ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag], ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag],
): ):
"""Calcule les moyennes/classements par tag d'un semestre de type 'Sx' """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 du semestre suivi
* pour les étudiants redoublants, c'est une fusion des moyennes/classements * pour les étudiants *redoublants* : une fusion des moyennes/classements
dans les (2) 'Sx' qu'il a suivi 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) * ``x`` est le rang (semestre_id) du semestre
du semestre et fid le formsemestre_id du semestre final (le plus récent) du * ``fid`` le formsemestre_id du semestre final (le plus récent) du regroupement.
regrouprement.
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. considérés sont uniquement ceux du semestre final.
Args: Args:
@ -85,6 +89,7 @@ class SxTag(TableTag):
self.rcf = rcf self.rcf = rcf
"""Le RCF sur lequel il s'appuie""" """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 # Les resultats des semestres taggués à prendre en compte dans le RCF
self.ressembuttags = {fid: ressembuttags[fid] for fid in rcf.semestres_aggreges} 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] self.ressembuttag_final = ressembuttags[self.fid_final]
"""Le ResSemBUTTag final""" """Le ResSemBUTTag final"""
self.etuds = ressembuttags[self.fid_final].etuds # Ajoute les etudids et les états civils
"""Les étudiants du ReSemBUTTag final""" self.etuds = self.ressembuttag_final.etuds
"""Les étudiants (extraits du ReSemBUTTag final)"""
# Ajout les etudids et les états civils
self.add_etuds(self.etuds) self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids) self.etudids_sorted = sorted(self.etudids)
"""Les etudids triés""" """Les etudids triés"""
@ -108,88 +112,93 @@ class SxTag(TableTag):
# Les tags # Les tags
self.tags_sorted = self.ressembuttag_final.tags_sorted 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)}") pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}")
# Les UE # Les UE données par leur acronyme
moy_sem_final = self.ressembuttag_final.moyennes_tags["but"] self.acronymes_sorted = self.ressembuttag_final.acronymes_sorted
self.ues = list(moy_sem_final.matrice_notes.columns) """Les acronymes des UEs (extraits du ResSemBUTTag final)"""
# L'association UE-compétences extraites du dernier semestre # 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 # Les coeffs pour la moyenne générale (traduisant également l'inscription
self.acronymes_ues_sorted = sorted(self.ues) # des étudiants aux UEs) (etudids_sorted x acronymes_ues_sorted)
self.matrice_coeffs_moy_gen = self.ressembuttag_final.matrice_coeffs_moy_gen
# 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"""
# Masque des inscriptions et des capitalisations # 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.etudids_sorted,
self.acronymes_ues_sorted, self.acronymes_sorted,
self.ressembuttags, self.ressembuttags,
self.fid_final, self.fid_final,
) )
self._aff_capitalisations() 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: for tag in self.tags_sorted:
# Y-a-t-il des notes ? # Y-a-t-il des notes ?
if not self.has_notes(tag): if not self.has_notes(tag):
pe_affichage.pe_print(f"> MoyTag 🏷{tag} actuellement sans ◯ notes") 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: else:
# Cube de note etudids x UEs # Cube de note etudids x UEs
notes_df, notes_cube = compute_notes_ues_cube( notes_df, notes_cube = compute_notes_ues_cube(
tag, tag,
self.etudids_sorted, self.etudids_sorted,
self.acronymes_ues_sorted, self.acronymes_sorted,
self.ressembuttags, self.ressembuttags,
) )
# self.ues_inscr_parcours = ~np.isnan(self.matrice_coeffs.to_numpy()) # Masque des inscriptions aux UEs (extraits de la matrice de coefficients)
# inscr_mask = self.ues_inscr_parcours inscr_mask: np.array = ~np.isnan(self.matrice_coeffs_moy_gen.to_numpy())
# Calcule des moyennes sous forme d'un dataframe # Matrice des moyennes
inscr_mask = ~np.isnan(self.ues_inscr_parcours_df.to_numpy())
matrice_moys_ues: pd.DataFrame = compute_notes_ues( matrice_moys_ues: pd.DataFrame = compute_notes_ues(
notes_cube, notes_cube,
masque_cube, masque_cube,
self.etudids_sorted, self.etudids_sorted,
self.acronymes_ues_sorted, self.acronymes_sorted,
inscr_mask, inscr_mask,
) )
# Les profils d'ects (pour debug) # Affichage de debug
profils_ects = [] self.__aff_profil_coeff_ects(tag)
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))
pe_affichage.pe_print(
f"> MoyTag 🏷{tag} avec "
+ f"ues={self.acronymes_ues_sorted} "
+ f"ects={profils_ects}"
)
# Les moyennes au tag # Mémorise les infos pour la moyennes au tag
self.moyennes_tags[tag] = MoyennesTag( self.moyennes_tags[tag] = MoyennesTag(
tag, matrice_moys_ues, self.matrice_coeffs_moy_gen 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={ues} " + f"inscr/ects={profils_ects}"
)
def has_notes(self, tag): def has_notes(self, tag):
"""Détermine si le SxTag, pour un tag donné, est en cours d'évaluation. """Détermine si le SxTag, pour un tag donné, est en cours d'évaluation.
@ -229,7 +238,7 @@ class SxTag(TableTag):
cap = [] cap = []
for frmsem_id in self.ressembuttags: for frmsem_id in self.ressembuttags:
if frmsem_id != self.fid_final: 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: if self.masque_df[frmsem_id].loc[etud.etudid, accr] > 0.0:
cap += [accr] cap += [accr]
if cap: if cap:
@ -239,7 +248,7 @@ class SxTag(TableTag):
def compute_notes_ues_cube( def compute_notes_ues_cube(
tag, etudids_sorted, acronymes_ues_sorted, ressembuttags tag, etudids_sorted, acronymes_sorted, ressembuttags
) -> (pd.DataFrame, np.array): ) -> (pd.DataFrame, np.array):
"""Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé) """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. nécessaire au calcul des moyennes du tag pour le RCS Sx.
@ -247,7 +256,7 @@ def compute_notes_ues_cube(
Args: Args:
etudids_sorted: La liste des etudids triés par ordre croissant (dim 0) 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) ressembuttags: Le dictionnaire des résultats de semestres BUT (tous tags confondus)
""" """
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2) # 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: for frmsem_id in semestres_id:
# Partant d'un dataframe vierge # 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 # Charge les notes du semestre tag
sem_tag = ressembuttags[frmsem_id] sem_tag = ressembuttags[frmsem_id]
@ -289,23 +298,27 @@ def compute_notes_ues_cube(
return dfs, etudids_x_ues_x_semestres return dfs, etudids_x_ues_x_semestres
def compute_masques_ues_cube( def compute_masques_capitalisation_cube(
etudids_sorted: list[int], etudids_sorted: list[int],
acronymes_ues_sorted: list[str], acronymes_sorted: list[str],
ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag], ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag],
formsemestre_id_final: int, formsemestre_id_final: int,
) -> (pd.DataFrame, np.array): ) -> (pd.DataFrame, np.array):
"""Construit le cube traduisant le masque des UEs à prendre en compte dans le calcul """Construit le cube traduisant les masques des UEs à prendre en compte dans le calcul
des moyennes, en utilisant le df capitalisations de chaque ResSemBUTTag 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: Args:
etudids_sorted: La liste des etudids triés par ordre croissant (dim 0) 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)
# ues_inscr_parcours_df: Le dataFrame des inscriptions au UE en fonction du parcours
ressembuttags: Le dictionnaire des résultats de semestres BUT (tous tags confondus) 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) # Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
# etudids_sorted = etudids_sorted # etudids_sorted = etudids_sorted
@ -317,15 +330,16 @@ def compute_masques_ues_cube(
for frmsem_id in semestres_id: for frmsem_id in semestres_id:
# Partant d'un dataframe contenant des 1.0 # Partant d'un dataframe contenant des 1.0
if frmsem_id == formsemestre_id_final: 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 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 = ressembuttags[frmsem_id].capitalisations
capitalisations = capitalisations.replace(True, 1.0).replace(False, 0.0) 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( etudids_communs, acronymes_communs = pe_comp.find_index_and_columns_communs(
df, capitalisations df, capitalisations
) )
@ -347,7 +361,7 @@ def compute_notes_ues(
set_cube: np.array, set_cube: np.array,
masque_cube: np.array, masque_cube: np.array,
etudids_sorted: list, etudids_sorted: list,
acronymes_ues_sorted: list, acronymes_sorted: list,
inscr_mask: np.array, inscr_mask: np.array,
): ):
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE """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 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 (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 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 inscr_mask: masque etudids x UE traduisant les inscriptions des
étudiants aux UE (du semestre terminal) étudiants aux UE (du semestre terminal)
Returns: Returns:
@ -369,7 +383,7 @@ def compute_notes_ues(
nb_etuds, nb_ues, nb_semestres = set_cube.shape nb_etuds, nb_ues, nb_semestres = set_cube.shape
nb_etuds_mask, nb_ues_mask = inscr_mask.shape nb_etuds_mask, nb_ues_mask = inscr_mask.shape
assert nb_etuds == len(etudids_sorted) 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_etuds == nb_etuds_mask
assert nb_ues == nb_ues_mask assert nb_ues == nb_ues_mask
@ -397,7 +411,7 @@ def compute_notes_ues(
etud_moy_tag_df = pd.DataFrame( etud_moy_tag_df = pd.DataFrame(
etud_moy, etud_moy,
index=etudids_sorted, # les etudids 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) 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: # Niveaux compétences:
if ue_infos.get("competence"): if ue_infos.get("competence"):
competence = referentiel_competence.competences.filter_by( competence = referentiel_competence.acronymes_ues_to_competences.filter_by(
titre=ue_infos["competence"] titre=ue_infos["competence"]
).first() ).first()
assert competence is not None # La compétence de titre indiqué doit exister assert competence is not None # La compétence de titre indiqué doit exister