Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
11 changed files with 940 additions and 727 deletions
Showing only changes of commit 5ea79c03a3 - Show all commits

View File

@ -41,6 +41,7 @@ from app.pe import pe_affichage
from app.pe.moys import pe_tabletags, pe_moy, pe_moytag, pe_sxtag from app.pe.moys import pe_tabletags, pe_moy, pe_moytag, pe_sxtag
from app.pe.rcss import pe_rcs from app.pe.rcss import pe_rcs
import app.pe.pe_comp as pe_comp import app.pe.pe_comp as pe_comp
from app.scodoc.sco_utils import ModuleType
class InterClassTag(pe_tabletags.TableTag): class InterClassTag(pe_tabletags.TableTag):
@ -119,7 +120,7 @@ class InterClassTag(pe_tabletags.TableTag):
# Les données sur les tags # Les données sur les tags
self.tags_sorted = self._do_taglist() self.tags_sorted = self._do_taglist()
"""Liste des tags (triés par ordre alphabétique)""" """Liste des tags (triés par ordre alphabétique)"""
aff = pe_affichage.aff_tag(self.tags_sorted) aff = pe_affichage.repr_tags(self.tags_sorted)
pe_affichage.pe_print(f"--> Tags : {aff}") pe_affichage.pe_print(f"--> Tags : {aff}")
# Les données sur les UEs (si SxTag) ou compétences (si RCSTag) # Les données sur les UEs (si SxTag) ou compétences (si RCSTag)
@ -134,8 +135,8 @@ class InterClassTag(pe_tabletags.TableTag):
f"--> Compétences : {pe_affichage.aff_competences(self.champs_sorted)}" f"--> Compétences : {pe_affichage.aff_competences(self.champs_sorted)}"
) )
# Construit la matrice de notes # Etudids triés
etudids_sorted = sorted(list(self.diplomes_ids)) self.etudids_sorted = sorted(list(self.diplomes_ids))
self.nom = self.get_repr() self.nom = self.get_repr()
"""Représentation textuelle de l'interclassement""" """Représentation textuelle de l'interclassement"""
@ -143,19 +144,24 @@ class InterClassTag(pe_tabletags.TableTag):
# Synthétise les moyennes/classements par tag # Synthétise les moyennes/classements par tag
self.moyennes_tags: dict[str, pe_moytag.MoyennesTag] = {} self.moyennes_tags: dict[str, pe_moytag.MoyennesTag] = {}
for tag in self.tags_sorted: for tag in self.tags_sorted:
notes = self.compute_notes_matrice(tag, etudids_sorted, self.champs_sorted) # Les moyennes tous modules confondus
notes_gen = self.compute_notes_matrice(tag, pole=None)
# Les ressources
notes_res = self.compute_notes_matrice(tag, pole=ModuleType.RESSOURCE)
# Les SAEs
notes_saes = self.compute_notes_matrice(tag, pole=ModuleType.SAE)
coeffs = self.compute_coeffs_matrice( # Les coefficients de la moyenne générale
tag, etudids_sorted, self.champs_sorted coeffs = self.compute_coeffs_matrice(tag)
) aff = pe_affichage.repr_profil_coeffs(coeffs, with_index=True)
aff = pe_affichage.aff_profil_coeffs(coeffs, with_index=True)
pe_affichage.pe_print(f"--> Moyenne 👜{tag} avec coeffs: {aff} ") pe_affichage.pe_print(f"--> Moyenne 👜{tag} avec coeffs: {aff} ")
self.moyennes_tags[tag] = pe_moytag.MoyennesTag( self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag, tag,
self.type, self.type,
notes, notes_gen,
notes_res,
notes_saes,
coeffs, # limite les moyennes aux étudiants de la promo coeffs, # limite les moyennes aux étudiants de la promo
) )
@ -163,29 +169,6 @@ class InterClassTag(pe_tabletags.TableTag):
"""Une représentation textuelle""" """Une représentation textuelle"""
return f"{self.nom_rcs} par {self.type}" return f"{self.nom_rcs} par {self.type}"
def __aff_profil_coeffs(self, matrice_coeffs_moy_gen):
"""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 des coeffs d'UE (pour debug)
profils = []
for i in matrice_coeffs_moy_gen.index:
val = matrice_coeffs_moy_gen.loc[i].fillna("-")
val = " | ".join([str(v) for v in val])
if val not in profils:
profils += [val]
# L'affichage
if len(profils) > 1:
profils_aff = "\n" + "\n".join([" " * 10 + prof for prof in profils])
else:
profils_aff = "\n".join(profils)
pe_affichage.pe_print(
f" > Moyenne calculée avec pour coeffs (de compétences) : {profils_aff}"
)
def _do_taglist(self): def _do_taglist(self):
"""Synthétise les tags à partir des TableTags (SXTag ou RCSTag) """Synthétise les tags à partir des TableTags (SXTag ou RCSTag)
@ -197,9 +180,7 @@ class InterClassTag(pe_tabletags.TableTag):
tags.extend(rcstag.tags_sorted) tags.extend(rcstag.tags_sorted)
return sorted(set(tags)) return sorted(set(tags))
def compute_notes_matrice( def compute_notes_matrice(self, tag, pole=None) -> pd.DataFrame:
self, tag, etudids_sorted: list[int], champs_sorted: list[str]
) -> pd.DataFrame:
"""Construit la matrice de notes (etudids x champs) en """Construit la matrice de notes (etudids x champs) en
reportant les moyennes obtenues par les étudiants reportant les moyennes obtenues par les étudiants
aux semestres de l'aggrégat pour le tag visé. aux semestres de l'aggrégat pour le tag visé.
@ -207,49 +188,56 @@ class InterClassTag(pe_tabletags.TableTag):
Les champs peuvent être des acronymes d'UEs ou des compétences. Les champs peuvent être des acronymes d'UEs ou des compétences.
Args: Args:
etudids_sorted: Les etudids des étudiants (diplômés) triés tag: Le tag visé
champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
Return: Return:
Le dataFrame (etudids x champs) Le dataFrame (etudids x champs)
reportant les moyennes des étudiants aux champs reportant les moyennes des étudiants aux champs
""" """
# etudids_sorted: Les etudids des étudiants (diplômés) triés
# champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
# Partant d'un dataframe vierge # Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids_sorted, columns=champs_sorted) df = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.champs_sorted)
for rcstag in self.rcstags.values(): for rcstag in self.rcstags.values():
# Charge les moyennes au tag d'un RCStag # Charge les moyennes au tag d'un RCStag
if tag in rcstag.moyennes_tags: if tag in rcstag.moyennes_tags:
moytag: pd.DataFrame = rcstag.moyennes_tags[tag].matrice_notes moytag = rcstag.moyennes_tags[tag]
notes: pd.DataFrame = None
if pole == ModuleType.RESSOURCE:
notes = moytag.matrice_notes_res
elif pole == ModuleType.SAE:
notes = moytag.matrice_notes_saes
else:
notes = moytag.matrice_notes_gen # dataframe etudids x ues
# Etudiants/Champs communs entre le RCSTag et les données interclassées # Etudiants/Champs communs entre le RCSTag et les données interclassées
( (
etudids_communs, etudids_communs,
champs_communs, champs_communs,
) = pe_comp.find_index_and_columns_communs(df, moytag) ) = pe_comp.find_index_and_columns_communs(df, notes)
# Injecte les notes par tag # Injecte les notes par tag
df.loc[etudids_communs, champs_communs] = moytag.loc[ df.loc[etudids_communs, champs_communs] = notes.loc[
etudids_communs, champs_communs etudids_communs, champs_communs
] ]
return df return df
def compute_coeffs_matrice( def compute_coeffs_matrice(self, tag) -> pd.DataFrame:
self, tag, etudids_sorted: list[int], champs_sorted: list[str]
) -> pd.DataFrame:
"""Idem que compute_notes_matrices mais pour les coeffs """Idem que compute_notes_matrices mais pour les coeffs
Args: Args:
etudids_sorted: Les etudids des étudiants (diplômés) triés tag: Le tag visé
champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
Return: Return:
Le dataFrame (etudids x champs) Le dataFrame (etudids x champs)
reportant les moyennes des étudiants aux champs reportant les moyennes des étudiants aux champs
""" """
# etudids_sorted: Les etudids des étudiants (diplômés) triés
# champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
# Partant d'un dataframe vierge # Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids_sorted, columns=champs_sorted) df = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.champs_sorted)
for rcstag in self.rcstags.values(): for rcstag in self.rcstags.values():
if tag in rcstag.moyennes_tags: if tag in rcstag.moyennes_tags:
@ -303,7 +291,7 @@ class InterClassTag(pe_tabletags.TableTag):
return None return None
def compute_df_synthese_moyennes_tag( def compute_df_synthese_moyennes_tag(
self, tag, aggregat=None, type_colonnes=False self, tag, pole, aggregat=None, type_colonnes=False
) -> pd.DataFrame: ) -> pd.DataFrame:
"""Construit le dataframe retraçant pour les données des moyennes """Construit le dataframe retraçant pour les données des moyennes
pour affichage dans la synthèse du jury PE. (cf. to_df()) pour affichage dans la synthèse du jury PE. (cf. to_df())
@ -338,6 +326,7 @@ class InterClassTag(pe_tabletags.TableTag):
if tag in rcstag.moyennes_tags: if tag in rcstag.moyennes_tags:
moytag: pd.DataFrame = rcstag.moyennes_tags[tag] moytag: pd.DataFrame = rcstag.moyennes_tags[tag]
df_moytag = moytag.to_df( df_moytag = moytag.to_df(
pole,
aggregat=aggregat, aggregat=aggregat,
cohorte="Groupe", cohorte="Groupe",
) )

View File

@ -4,7 +4,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.pe.moys import pe_moy from app.pe.moys import pe_moy
from app.scodoc.sco_utils import ModuleType
CODE_MOY_UE = "UEs" CODE_MOY_UE = "UEs"
CODE_MOY_COMPETENCES = "Compétences" CODE_MOY_COMPETENCES = "Compétences"
@ -16,7 +16,9 @@ class MoyennesTag:
self, self,
tag: str, tag: str,
type_moyenne: str, type_moyenne: str,
matrice_notes: pd.DataFrame, # etudids x colonnes matrice_notes_gen: pd.DataFrame, # etudids x colonnes
matrice_notes_res: pd.DataFrame,
matrice_notes_saes: pd.DataFrame,
matrice_coeffs: pd.DataFrame, # etudids x colonnes matrice_coeffs: pd.DataFrame, # etudids x colonnes
): ):
"""Classe centralisant la synthèse des moyennes/classements d'une série """Classe centralisant la synthèse des moyennes/classements d'une série
@ -26,7 +28,11 @@ class MoyennesTag:
Args: Args:
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_gen: Les moyennes (etudid x acronymes_ues ou etudid x compétences)
aux différentes UEs ou compétences (indépendamment des ressources
ou SAEs)
matrice_notes_res: Les moyennes limitées aux ressources
matrice_notes_saes: Les moyennes limitées aux saes
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)
""" """
@ -36,45 +42,94 @@ class MoyennesTag:
self.type = type_moyenne self.type = type_moyenne
"""Le type de moyennes (par UEs ou par compétences)""" """Le type de moyennes (par UEs ou par compétences)"""
# Les moyennes par UE # Les moyennes par UE/compétences (ressources/SAEs confondues)
self.matrice_notes: pd.DataFrame = matrice_notes self.matrice_notes_gen: pd.DataFrame = matrice_notes_gen
"""Les notes aux UEs ou aux compétences (DataFrame)""" """Les notes par UEs ou Compétences (DataFrame)"""
# Les moyennes par UE/compétences (limitées aux ressources)
self.matrice_notes_res: pd.DataFrame = matrice_notes_res
"""Les notes aux ressources par UEs ou Compétences"""
# Les moyennes par UE/compétences (limitées aux SAEs)
self.matrice_notes_saes: pd.DataFrame = matrice_notes_saes
"""Les notes aux SAEs par UEs ou Compétences"""
self.matrice_coeffs_moy_gen: pd.DataFrame = matrice_coeffs self.matrice_coeffs_moy_gen: pd.DataFrame = matrice_coeffs
"""Les coeffs à appliquer pour le calcul des moyennes générales """Les coeffs à appliquer pour le calcul des moyennes générales
(toutes UE ou compétences confondues). NaN si étudiant non inscrit""" (toutes UE ou compétences confondues). NaN si étudiant non inscrit"""
self.moyennes: dict[int, pd.DataFrame] = {} self.moyennes_gen: dict[int, pd.DataFrame] = {}
"""Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs""" """Dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs"""
self.etudids = self.matrice_notes.index self.moyennes_res: dict[int, pd.DataFrame] = {}
"""Dataframes retraçant les moyennes/classements/statistiques des étudiants, limitées aux ressources"""
self.moyennes_saes: dict[int, pd.DataFrame] = {}
"""Dataframes retraçant les moyennes/classements/statistiques des étudiants, limitées aux SAEs"""
self.etudids = self.matrice_notes_gen.index
"""Les étudids renseignés dans les moyennes""" """Les étudids renseignés dans les moyennes"""
self.champs = self.matrice_notes.columns self.champs = self.matrice_notes_gen.columns
"""Les champs (acronymes d'UE ou compétences) renseignés dans les moyennes""" """Les champs (acronymes d'UE ou compétences) renseignés dans les moyennes"""
for col in self.champs: # if ue.type != UE_SPORT: for col in self.champs: # if ue.type != UE_SPORT:
notes = matrice_notes[col] # Les moyennes tous modules confondus
self.moyennes[col] = pe_moy.Moyenne(notes) notes = matrice_notes_gen[col]
self.moyennes_gen[col] = pe_moy.Moyenne(notes)
# par ressources
notes = matrice_notes_res[col]
self.moyennes_res[col] = pe_moy.Moyenne(notes)
# par SAEs
notes = matrice_notes_saes[col]
self.moyennes_saes[col] = pe_moy.Moyenne(notes)
# Les moyennes générales # Les moyennes générales (toutes UEs confondues)
notes_gen = pd.Series(np.nan, index=self.matrice_notes.index) self.notes_gen = pd.Series(np.nan, index=self.matrice_notes_gen.index)
"""Les notes générales (moyenne toutes UEs confonudes)""" if self.has_notes(pole=None):
if self.has_notes(): self.notes_gen = self.compute_moy_gen(
notes_gen = self.compute_moy_gen( self.matrice_notes_gen, self.matrice_coeffs_moy_gen
self.matrice_notes, self.matrice_coeffs_moy_gen
) )
self.notes_gen = notes_gen self.moyenne_gen = pe_moy.Moyenne(self.notes_gen)
self.moyenne_gen = pe_moy.Moyenne(notes_gen) """Dataframe retraçant les moyennes/classements/statistiques général (toutes UESs confondues et modules confondus)"""
"""Le dataframe retraçant les moyennes/classements/statistiques général"""
def has_notes(self): self.notes_res = pd.Series(np.nan, index=self.matrice_notes_res.index)
if self.has_notes(pole=ModuleType.RESSOURCE):
self.notes_res = self.compute_moy_gen(
self.matrice_notes_res, self.matrice_coeffs_moy_gen
)
self.moyenne_res = pe_moy.Moyenne(self.notes_res)
"""Dataframe retraçant les moyennes/classements/statistiques général (toutes UESs confondues et uniquement sur les ressources)"""
self.notes_saes = pd.Series(np.nan, index=self.matrice_notes_saes.index)
if self.has_notes(pole=ModuleType.SAE):
self.notes_saes = self.compute_moy_gen(
self.matrice_notes_saes, self.matrice_coeffs_moy_gen
)
self.moyenne_saes = pe_moy.Moyenne(self.notes_saes)
"""Dataframe retraçant les moyennes/classements/statistiques général (toutes UESs confondues et uniquement sur les SAEs)"""
def has_notes(self, pole):
"""Détermine si les moyennes (aux UEs ou aux compétences) """Détermine si les moyennes (aux UEs ou aux compétences)
ont des notes ont des notes
`pole` détermine les modules pris en compte :
* si `pole` vaut `ModuleType.RESSOURCE`, seules les ressources sont prises
en compte (moyenne de ressources par UEs)
* si `pole` vaut `ModuleType.SAE`, seules les SAEs sont prises en compte
* si `pole` vaut `None` (ou toute autre valeur),
tous les modules sont pris en compte (moyenne d'UEs)
Returns: Returns:
True si la moytag a des notes, False sinon True si la moytag a des notes, False sinon
""" """
notes = self.matrice_notes if pole == ModuleType.RESSOURCE:
notes = self.matrice_notes_res
elif pole == ModuleType.SAE:
notes = self.matrice_notes_saes
else:
notes = self.matrice_notes_gen
nbre_nan = notes.isna().sum().sum() nbre_nan = notes.isna().sum().sum()
nbre_notes_potentielles = len(notes.index) * len(notes.columns) nbre_notes_potentielles = len(notes.index) * len(notes.columns)
if nbre_nan == nbre_notes_potentielles: if nbre_nan == nbre_notes_potentielles:
@ -107,11 +162,14 @@ class MoyennesTag:
return moy_gen_tag return moy_gen_tag
def to_df(self, aggregat=None, cohorte=None) -> pd.DataFrame: def to_df(self, pole, aggregat=None, cohorte=None) -> pd.DataFrame:
"""Renvoie le df synthétisant l'ensemble des données """Renvoie le df synthétisant l'ensemble des données
connues connues
Adapte les intitulés des colonnes aux données fournies Adapte les intitulés des colonnes aux données fournies
(nom d'aggrégat, type de cohorte). (nom d'aggrégat, type de cohorte).
`pole` détermine les modules à prendre en compte dans la moyenne (None=tous,
RESSOURCES ou SAES)
""" """
etudids_sorted = sorted(self.etudids) etudids_sorted = sorted(self.etudids)
@ -121,19 +179,30 @@ class MoyennesTag:
# Ajout des notes pour tous les champs # Ajout des notes pour tous les champs
champs = list(self.champs) champs = list(self.champs)
for champ in champs: for champ in champs:
df_champ = self.moyennes[champ].get_df_synthese() # le dataframe if pole == ModuleType.RESSOURCE:
df_champ = self.moyennes_res[champ].get_df_synthese()
elif pole == ModuleType.SAE:
df_champ = self.moyennes_saes[champ].get_df_synthese()
else:
df_champ = self.moyennes_gen[champ].get_df_synthese() # le dataframe
# Renomme les colonnes # Renomme les colonnes
cols = [ cols = [
get_colonne_df(aggregat, self.tag, champ, cohorte, critere) get_colonne_df(aggregat, pole, self.tag, champ, cohorte, critere)
for critere in pe_moy.Moyenne.COLONNES_SYNTHESE for critere in pe_moy.Moyenne.COLONNES_SYNTHESE
] ]
df_champ.columns = cols df_champ.columns = cols
df = df.join(df_champ) df = df.join(df_champ)
# Ajoute la moy générale # Ajoute la moy générale
df_moy_gen = self.moyenne_gen.get_df_synthese() df_moy_gen: pd.DataFrame = None
if pole == ModuleType.RESSOURCE:
df_moy_gen = self.moyenne_res.get_df_synthese()
elif pole == ModuleType.SAE:
df_moy_gen = self.moyenne_saes.get_df_synthese()
else:
df_moy_gen = self.moyenne_gen.get_df_synthese()
cols = [ cols = [
get_colonne_df(aggregat, self.tag, CHAMP_GENERAL, cohorte, critere) get_colonne_df(aggregat, pole, self.tag, CHAMP_GENERAL, cohorte, critere)
for critere in pe_moy.Moyenne.COLONNES_SYNTHESE for critere in pe_moy.Moyenne.COLONNES_SYNTHESE
] ]
df_moy_gen.columns = cols df_moy_gen.columns = cols
@ -142,12 +211,18 @@ class MoyennesTag:
return df return df
def get_colonne_df(aggregat, tag, champ, cohorte, critere): def get_colonne_df(aggregat, pole, tag, champ, cohorte, critere):
"""Renvoie le tuple (aggregat, tag, champ, cohorte, critere) """Renvoie le tuple (aggregat, tag, champ, cohorte, critere)
utilisé pour désigner les colonnes du df""" utilisé pour désigner les colonnes du df"""
liste_champs = [] liste_champs = []
if aggregat != None: if aggregat != None:
liste_champs += [aggregat] liste_champs += [aggregat]
if pole == ModuleType.RESSOURCE:
liste_champs += ["ressources"]
elif pole == ModuleType.SAE:
liste_champs += ["saes"]
else:
liste_champs += ["global"]
liste_champs += [tag, champ] liste_champs += [tag, champ]
if cohorte != None: if cohorte != None:
liste_champs += [cohorte] liste_champs += [cohorte]

View File

@ -44,6 +44,7 @@ from app.pe.rcss import pe_rcs, pe_rcsemx
import app.pe.moys.pe_sxtag as pe_sxtag import app.pe.moys.pe_sxtag as pe_sxtag
import app.pe.pe_comp as pe_comp import app.pe.pe_comp as pe_comp
from app.pe.moys import pe_tabletags, pe_moytag from app.pe.moys import pe_tabletags, pe_moytag
from app.scodoc.sco_utils import ModuleType
class RCSemXTag(pe_tabletags.TableTag): class RCSemXTag(pe_tabletags.TableTag):
@ -117,8 +118,8 @@ class RCSemXTag(pe_tabletags.TableTag):
set(self.acronymes_ues_to_competences.values()) set(self.acronymes_ues_to_competences.values())
) )
"""Compétences (triées par nom, extraites des SxTag aggrégés)""" """Compétences (triées par nom, extraites des SxTag aggrégés)"""
self._aff_comp_et_ues_debug() aff = pe_affichage.repr_comp_et_ues(self.acronymes_ues_to_competences)
# 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()
@ -137,43 +138,45 @@ class RCSemXTag(pe_tabletags.TableTag):
# ****************************************** # ******************************************
# Cube d'inscription (etudids_sorted x compétences_sorted x sxstags) # Cube d'inscription (etudids_sorted x compétences_sorted x sxstags)
# indiquant quel sxtag est valide pour chaque étudiant # indiquant quel sxtag est valide pour chaque étudiant
inscriptions_df, inscriptions_cube = self.compute_inscriptions_comps_cube( inscr_df, inscr_cube = self.compute_inscriptions_comps_cube(tag)
tag, self.etudids_sorted, self.competences_sorted, self.sxstags_aggreges
)
# Traitement des notes # Traitement des notes
# ******************** # ********************
# Cube de notes (etudids_sorted x compétences_sorted x sxstags) ### Moyennes tous modules confondus
notes_df, notes_cube = self.compute_notes_comps_cube( # Cube de notes (etudids_sorted x compétences_sorted x sxstags)
tag, self.etudids_sorted, self.competences_sorted, self.sxstags_aggreges notes_df, notes_cube = self.compute_notes_comps_cube(tag, mode=None)
)
# Calcule les moyennes sous forme d'un dataframe en les "aggrégant" # Calcule les moyennes sous forme d'un dataframe en les "aggrégant"
# compétence par compétence # compétence par compétence
moys_competences = compute_notes_competences( moys_competences = self.compute_notes_competences(notes_cube, inscr_cube)
notes_cube,
inscriptions_cube, ## Moyennes des ressources
self.etudids_sorted, notes_df_res, notes_cube_res = self.compute_notes_comps_cube(
self.competences_sorted, tag, mode=ModuleType.RESSOURCE
)
moys_competences_res = self.compute_notes_competences(
notes_cube_res, inscr_cube
)
## Moyennes des SAEs
notes_df_sae, notes_cube_sae = self.compute_notes_comps_cube(
tag, mode=ModuleType.SAE
)
moys_competences_saes = self.compute_notes_competences(
notes_cube_sae, inscr_cube
) )
# Traitement des coeffs pour la moyenne générale # Traitement des coeffs pour la moyenne générale
# *********************************************** # ***********************************************
# Df des coeffs sur tous les SxTags aggrégés # Df des coeffs sur tous les SxTags aggrégés
coeffs_df, coeffs_cube = self.compute_coeffs_comps_cube( coeffs_df, coeffs_cube = self.compute_coeffs_comps_cube(tag)
tag,
self.etudids_sorted,
self.competences_sorted,
self.sxstags_aggreges,
)
# Synthèse des coefficients à prendre en compte pour la moyenne générale # Synthèse des coefficients à prendre en compte pour la moyenne générale
matrice_coeffs_moy_gen = compute_coeffs_competences( matrice_coeffs_moy_gen = self.compute_coeffs_competences(
coeffs_cube, coeffs_cube, inscr_cube, notes_cube
inscriptions_cube,
notes_cube,
self.etudids_sorted,
self.competences_sorted,
) )
aff = pe_affichage.aff_profil_coeffs(
# Affichage des coeffs
aff = pe_affichage.repr_profil_coeffs(
matrice_coeffs_moy_gen, with_index=True matrice_coeffs_moy_gen, with_index=True
) )
pe_affichage.pe_print(f" > Moyenne calculée avec pour coeffs : {aff}") pe_affichage.pe_print(f" > Moyenne calculée avec pour coeffs : {aff}")
@ -183,6 +186,8 @@ class RCSemXTag(pe_tabletags.TableTag):
tag, tag,
pe_moytag.CODE_MOY_COMPETENCES, pe_moytag.CODE_MOY_COMPETENCES,
moys_competences, moys_competences,
moys_competences_res,
moys_competences_saes,
matrice_coeffs_moy_gen, matrice_coeffs_moy_gen,
) )
@ -200,35 +205,42 @@ class RCSemXTag(pe_tabletags.TableTag):
else: else:
return f"{self.__class__.__name__} {self.rcs_id}" return f"{self.__class__.__name__} {self.rcs_id}"
def compute_notes_comps_cube( def compute_notes_comps_cube(self, tag, mode=None):
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) """Pour un tag donné, construit le cube de notes (etudid x competences x SxTag)
nécessaire au calcul des moyennes, nécessaire au calcul des moyennes,
en remplaçant les données d'UE (obtenus du SxTag) par les compétences en remplaçant les données d'UE (obtenus du SxTag) par les compétences
`mode` détermine les modules pris en compte :
* si `mode` vaut `ModuleType.RESSOURCE`, seules les ressources sont prises
en compte (moyenne de ressources par UEs)
* si `mode` vaut `ModuleType.SAE`, seules les SAEs sont prises en compte
* si `mode` vaut `None` (ou toute autre valeur),
tous les modules sont pris en compte (moyenne d'UEs)
Args: Args:
tag: Le tag visé tag: Le tag visé
etudids_sorted: Les etudis triés (dim 0)
competences_sorted: Les compétences triées (dim 1)
sxstags: Les SxTag à réunir
""" """
# etudids_sorted: list[int],
# competences_sorted: list[str],
# sxstags: dict[(str, int) : pe_sxtag.SxTag],
notes_dfs = {} notes_dfs = {}
for sxtag_id, sxtag in sxstags.items(): for sxtag_id, sxtag in self.sxstags_aggreges.items():
# Partant d'un dataframe vierge # Partant d'un dataframe vierge
notes_df = pd.DataFrame( notes_df = pd.DataFrame(
np.nan, index=etudids_sorted, columns=competences_sorted np.nan, index=self.etudids_sorted, columns=self.competences_sorted
) )
# Charge les notes du semestre tag (copie car changement de nom de colonnes à venir) # Charge les notes du semestre tag (copie car changement de nom de colonnes à venir)
if tag in sxtag.moyennes_tags: # si le tag est présent dans le semestre if tag in sxtag.moyennes_tags: # si le tag est présent dans le semestre
moys_tag = sxtag.moyennes_tags[tag] moys_tag = sxtag.moyennes_tags[tag]
notes = moys_tag.matrice_notes.copy() # avec une copie if mode == ModuleType.RESSOURCE:
notes = moys_tag.matrice_notes_res.copy() # avec une copie
elif mode == ModuleType.SAE:
notes = moys_tag.matrice_notes_saes.copy()
else:
notes = moys_tag.matrice_notes_gen.copy() # dataframe etudids x ues
# Traduction des acronymes d'UE en compétences # Traduction des acronymes d'UE en compétences
acronymes_ues_columns = notes.columns acronymes_ues_columns = notes.columns
@ -257,18 +269,14 @@ class RCSemXTag(pe_tabletags.TableTag):
notes_dfs[sxtag_id] = notes_df notes_dfs[sxtag_id] = notes_df
"""Réunit les notes sous forme d'un cube etudids x competences x semestres""" """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] sxtag_x_etudids_x_comps = [
notes_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
]
notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1) notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
return notes_dfs, notes_etudids_x_comps_x_sxtag return notes_dfs, notes_etudids_x_comps_x_sxtag
def compute_coeffs_comps_cube( def compute_coeffs_comps_cube(self, tag):
self,
tag,
etudids_sorted: list[int],
competences_sorted: list[str],
sxstags: dict[(str, int) : pe_sxtag.SxTag],
):
"""Pour un tag donné, construit """Pour un tag donné, construit
le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions
des étudiants aux UEs en fonction de leur parcours) des étudiants aux UEs en fonction de leur parcours)
@ -277,16 +285,17 @@ class RCSemXTag(pe_tabletags.TableTag):
Args: Args:
tag: Le tag visé tag: Le tag visé
etudids_sorted: Les etudis triés
competences_sorted: Les compétences triées
sxstags: Les SxTag à réunir
""" """
# etudids_sorted: list[int],
# competences_sorted: list[str],
# sxstags: dict[(str, int) : pe_sxtag.SxTag],
coeffs_dfs = {} coeffs_dfs = {}
for sxtag_id, sxtag in sxstags.items(): for sxtag_id, sxtag in self.sxstags_aggreges.items():
# Partant d'un dataframe vierge # Partant d'un dataframe vierge
coeffs_df = pd.DataFrame( coeffs_df = pd.DataFrame(
np.nan, index=etudids_sorted, columns=competences_sorted np.nan, index=self.etudids_sorted, columns=self.competences_sorted
) )
if tag in sxtag.moyennes_tags: if tag in sxtag.moyennes_tags:
moys_tag = sxtag.moyennes_tags[tag] moys_tag = sxtag.moyennes_tags[tag]
@ -316,7 +325,9 @@ class RCSemXTag(pe_tabletags.TableTag):
coeffs_dfs[sxtag_id] = coeffs_df coeffs_dfs[sxtag_id] = coeffs_df
"""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[sxtag_id] for sxtag_id in sxstags] sxtag_x_etudids_x_comps = [
coeffs_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
]
coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1) coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
return coeffs_dfs, coeffs_etudids_x_comps_x_sxtag return coeffs_dfs, coeffs_etudids_x_comps_x_sxtag
@ -324,9 +335,6 @@ class RCSemXTag(pe_tabletags.TableTag):
def compute_inscriptions_comps_cube( def compute_inscriptions_comps_cube(
self, self,
tag, tag,
etudids_sorted: list[int],
competences_sorted: list[str],
sxstags: dict[(str, int) : pe_sxtag.SxTag],
): ):
"""Pour un tag donné, construit """Pour un tag donné, construit
le cube etudid x competences x SxTag traduisant quels sxtags est à prendre le cube etudid x competences x SxTag traduisant quels sxtags est à prendre
@ -335,24 +343,24 @@ class RCSemXTag(pe_tabletags.TableTag):
Args: Args:
tag: Le tag visé tag: Le tag visé
etudids_sorted: Les etudis triés
competences_sorted: Les compétences triées
sxstags: Les SxTag à réunir
""" """
# etudids_sorted: list[int],
# competences_sorted: list[str],
# sxstags: dict[(str, int) : pe_sxtag.SxTag],
# Initialisation # Initialisation
inscriptions_dfs = {} inscriptions_dfs = {}
for sxtag_id, sxtag in sxstags.items(): for sxtag_id, sxtag in self.sxstags_aggreges.items():
# Partant d'un dataframe vierge # Partant d'un dataframe vierge
inscription_df = pd.DataFrame( inscription_df = pd.DataFrame(
0, index=etudids_sorted, columns=competences_sorted 0, index=self.etudids_sorted, columns=self.competences_sorted
) )
# Les étudiants dont les résultats au sxtag ont été calculés # Les étudiants dont les résultats au sxtag ont été calculés
etudids_sxtag = sxtag.etudids_sorted etudids_sxtag = sxtag.etudids_sorted
# Les étudiants communs # Les étudiants communs
etudids_communs = sorted(set(etudids_sorted) & set(etudids_sxtag)) etudids_communs = sorted(set(self.etudids_sorted) & set(etudids_sxtag))
# Acte l'inscription # Acte l'inscription
inscription_df.loc[etudids_communs, :] = 1 inscription_df.loc[etudids_communs, :] = 1
@ -361,7 +369,9 @@ class RCSemXTag(pe_tabletags.TableTag):
inscriptions_dfs[sxtag_id] = inscription_df inscriptions_dfs[sxtag_id] = inscription_df
"""Réunit les inscriptions sous forme d'un cube etudids x competences x semestres""" """Réunit les inscriptions sous forme d'un cube etudids x competences x semestres"""
sxtag_x_etudids_x_comps = [inscriptions_dfs[sxtag_id] for sxtag_id in sxstags] sxtag_x_etudids_x_comps = [
inscriptions_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
]
inscriptions_etudids_x_comps_x_sxtag = np.stack( inscriptions_etudids_x_comps_x_sxtag = np.stack(
sxtag_x_etudids_x_comps, axis=-1 sxtag_x_etudids_x_comps, axis=-1
) )
@ -392,117 +402,97 @@ class RCSemXTag(pe_tabletags.TableTag):
dict_competences |= sxtag.acronymes_ues_to_competences dict_competences |= sxtag.acronymes_ues_to_competences
return dict_competences return dict_competences
def _aff_comp_et_ues_debug(self): def compute_notes_competences(self, set_cube: np.array, inscriptions: np.array):
"""Affichage pour debug""" """Calcule la moyenne par compétences (à un tag donné) sur plusieurs semestres (partant du set_cube).
aff_comp = []
for comp in self.competences_sorted: La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles
liste = []
for acro in self.acronymes_ues_to_competences: *Remarque* : Adaptation de moy_ue.compute_ue_moys_apc au cas des moyennes de tag
if self.acronymes_ues_to_competences[acro] == comp: par aggrégat de plusieurs semestres.
liste += ["📍" + acro]
aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"] Args:
pe_affichage.pe_print(f"--> Compétences :") set_cube: notes moyennes aux compétences ndarray
pe_affichage.pe_print("\n".join(aff_comp)) (etuds x UEs|compétences x sxtags), des floats avec des NaN
inscriptions: inscrptions aux compétences ndarray
(etuds x UEs|compétences x sxtags), des 0 et des 1
Returns:
Un DataFrame avec pour columns les moyennes par tags,
et pour rows les etudid
"""
# etudids_sorted: liste des étudiants (dim. 0 du cube)
# competences_sorted: list (dim. 1 du cube)
nb_etuds, nb_comps, nb_semestres = set_cube.shape
# assert nb_etuds == len(etudids_sorted)
# assert nb_comps == len(competences_sorted)
# Applique le masque d'inscriptions
set_cube_significatif = set_cube * inscriptions
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube_significatif)
# Enlève les NaN du cube de notes pour les entrées manquantes
set_cube_no_nan = np.nan_to_num(set_cube_significatif, nan=0.0)
# Les moyennes par tag
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2)
# Le dataFrame des notes moyennes
etud_moy_tag_df = pd.DataFrame(
etud_moy_tag,
index=self.etudids_sorted, # les etudids
columns=self.competences_sorted, # les competences
)
etud_moy_tag_df.fillna(np.nan)
return etud_moy_tag_df
def compute_coeffs_competences(
self,
coeff_cube: np.array,
inscriptions: np.array,
set_cube: np.array,
):
"""Calcule les coeffs à utiliser pour la moyenne générale (toutes compétences
confondues), en fonction des inscriptions.
Args:
coeffs_cube: coeffs impliqués dans la moyenne générale (semestres par semestres)
inscriptions: inscriptions aux UES|Compétences ndarray
(etuds x UEs|compétences x sxtags), des 0 ou des 1
set_cube: les notes
def compute_coeffs_competences( Returns:
coeff_cube: np.array, Un DataFrame de coefficients (etudids_sorted x compétences_sorted)
inscriptions: np.array, """
set_cube: np.array, # etudids_sorted: liste des étudiants (dim. 0 du cube)
etudids_sorted: list, # competences_sorted: list (dim. 1 du cube)
competences_sorted: list, nb_etuds, nb_comps, nb_semestres = inscriptions.shape
): # assert nb_etuds == len(etudids_sorted)
"""Calcule les coeffs à utiliser pour la moyenne générale (toutes compétences # assert nb_comps == len(competences_sorted)
confondues), en fonction des inscriptions.
Args: # Applique le masque des inscriptions aux coeffs et aux notes
coeffs_cube: coeffs impliqués dans la moyenne générale (semestres par semestres) coeffs_significatifs = coeff_cube * inscriptions
inscriptions: inscriptions aux UES|Compétences ndarray
(etuds x UEs|compétences x sxtags), des 0 ou des 1
set_cube: les notes
etudids_sorted: liste des étudiants (dim. 0 du cube)
competences_sorted: list (dim. 1 du cube)
Returns: # Enlève les NaN du cube de notes pour les entrées manquantes
Un DataFrame de coefficients (etudids_sorted x compétences_sorted) coeffs_cube_no_nan = np.nan_to_num(coeffs_significatifs, nan=0.0)
"""
nb_etuds, nb_comps, nb_semestres = inscriptions.shape
assert nb_etuds == len(etudids_sorted)
assert nb_comps == len(competences_sorted)
# Applique le masque des inscriptions aux coeffs et aux notes # Quelles entrées du cube contiennent des notes ?
coeffs_significatifs = coeff_cube * inscriptions mask = ~np.isnan(set_cube)
# Enlève les NaN du cube de notes pour les entrées manquantes # Retire les coefficients associés à des données sans notes
coeffs_cube_no_nan = np.nan_to_num(coeffs_significatifs, nan=0.0) coeffs_cube_no_nan = coeffs_cube_no_nan * mask
# Quelles entrées du cube contiennent des notes ? # Somme les coefficients (correspondant à des notes)
mask = ~np.isnan(set_cube) coeff_tag = np.sum(coeffs_cube_no_nan, axis=2)
# Retire les coefficients associés à des données sans notes # Le dataFrame des coeffs
coeffs_cube_no_nan = coeffs_cube_no_nan * mask coeffs_df = pd.DataFrame(
coeff_tag, index=self.etudids_sorted, columns=self.competences_sorted
)
# Remet à Nan les coeffs à 0
coeffs_df = coeffs_df.fillna(np.nan)
# Somme les coefficients (correspondant à des notes) return coeffs_df
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 = coeffs_df.fillna(np.nan)
return coeffs_df
def compute_notes_competences(
set_cube: np.array,
inscriptions: 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 moyenne est un nombre (note/20), ou NaN si pas de notes disponibles
*Remarque* : Adaptation de moy_ue.compute_ue_moys_apc au cas des moyennes de tag
par aggrégat de plusieurs semestres.
Args:
set_cube: notes moyennes aux compétences ndarray
(etuds x UEs|compétences x sxtags), des floats avec des NaN
inscriptions: inscrptions aux compétences ndarray
(etuds x UEs|compétences x sxtags), des 0 et des 1
etudids_sorted: liste des étudiants (dim. 0 du cube)
competences_sorted: list (dim. 1 du cube)
Returns:
Un DataFrame avec pour columns les moyennes par tags,
et pour rows les etudid
"""
nb_etuds, nb_comps, nb_semestres = set_cube.shape
assert nb_etuds == len(etudids_sorted)
assert nb_comps == len(competences_sorted)
# Applique le masque d'inscriptions
set_cube_significatif = set_cube * inscriptions
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube_significatif)
# Enlève les NaN du cube de notes pour les entrées manquantes
set_cube_no_nan = np.nan_to_num(set_cube_significatif, nan=0.0)
# Les moyennes par tag
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2)
# Le dataFrame des notes moyennes
etud_moy_tag_df = pd.DataFrame(
etud_moy_tag,
index=etudids_sorted, # les etudids
columns=competences_sorted, # les competences
)
etud_moy_tag_df.fillna(np.nan)
return etud_moy_tag_df

View File

@ -1,4 +1,4 @@
# -*- mode: python -*- # -*- pole: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################## ##############################################################################
@ -46,6 +46,7 @@ import app.pe.pe_etudiant as pe_etudiant
from app.pe.moys import pe_tabletags, pe_moytag from app.pe.moys import pe_tabletags, pe_moytag
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.scodoc import codes_cursus as sco_codes from app.scodoc import codes_cursus as sco_codes
from app.scodoc.sco_utils import *
class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
@ -79,9 +80,20 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
] ]
"""Liste des UEs standards du ResultatsSemestreBUT""" """Liste des UEs standards du ResultatsSemestreBUT"""
# Les parcours des étudiants à ce semestre
self.parcours = []
"""Parcours auxquels sont inscrits les étudiants"""
for etudid in self.etudids_sorted:
parcour = self.formsemestre.etuds_inscriptions[etudid].parcour
if parcour:
self.parcours += [parcour.libelle]
else:
self.parcours += [None]
# 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""" """Inscription des étudiants aux UEs des parcours"""
# Les acronymes des UEs # Les acronymes des UEs
self.ues_to_acronymes = {ue.id: ue.acronyme for ue in self.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())
@ -89,7 +101,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
# Les compétences associées aux UEs (définies par les acronymes) # Les compétences associées aux UEs (définies par les acronymes)
self.acronymes_ues_to_competences = {} self.acronymes_ues_to_competences = {}
"""L'association acronyme d'UEs -> compétence""" """Association acronyme d'UEs -> compétence"""
for ue in self.ues_standards: for ue in self.ues_standards:
assert ue.niveau_competence, ScoValueError( assert ue.niveau_competence, ScoValueError(
"Des UEs ne sont pas rattachées à des compétences" "Des UEs ne sont pas rattachées à des compétences"
@ -99,12 +111,15 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
self.competences_sorted = sorted( self.competences_sorted = sorted(
list(set(self.acronymes_ues_to_competences.values())) list(set(self.acronymes_ues_to_competences.values()))
) )
"""Les compétences triées par nom""" """Compétences triées par nom"""
self._aff_ue_et_comp_debug() aff = pe_affichage.repr_asso_ue_comp(self.acronymes_ues_to_competences)
pe_affichage.pe_print(f"--> UEs/Compétences : {aff}")
# 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._aff_tags_debug(tags_dict) pe_affichage.pe_print(
f"""--> {pe_affichage.aff_tags_par_categories(tags_dict)}"""
)
self._check_tags(tags_dict) self._check_tags(tags_dict)
# Les coefficients pour le calcul de la moyenne générale, donnés par # Les coefficients pour le calcul de la moyenne générale, donnés par
@ -113,7 +128,10 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
self.ues_inscr_parcours_df, self.ues_standards self.ues_inscr_parcours_df, self.ues_standards
) )
"""DataFrame indiquant les coeffs des UEs par ordre alphabétique d'acronyme""" """DataFrame indiquant les coeffs des UEs par ordre alphabétique d'acronyme"""
self.__aff_profil_coeffs() profils_aff = pe_affichage.repr_profil_coeffs(self.matrice_coeffs_moy_gen)
pe_affichage.pe_print(
f"--> Moyenne générale calculée avec pour coeffs d'UEs : {profils_aff}"
)
# 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(self.ues_standards) self.capitalisations = self._get_capitalisations(self.ues_standards)
@ -121,24 +139,44 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
# 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')""" """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] info_tag = tags_dict["personnalises"][tag]
moy_ues_tag = self.compute_moy_ues_tag(infos_tag) # Les moyennes générales par UEs
moy_ues_tag = self.compute_moy_ues_tag(info_tag=info_tag, pole=None)
# Les moyennes par ressources de chaque UE
moy_res_tag = self.compute_moy_ues_tag(
info_tag=info_tag, pole=ModuleType.RESSOURCE
)
# Les moyennes par SAEs de chaque UE
moy_saes_tag = self.compute_moy_ues_tag(
info_tag=info_tag, pole=ModuleType.SAE
)
self.moyennes_tags[tag] = pe_moytag.MoyennesTag( self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag, pe_moytag.CODE_MOY_UE, moy_ues_tag, self.matrice_coeffs_moy_gen tag,
pe_moytag.CODE_MOY_UE,
moy_ues_tag,
moy_res_tag,
moy_saes_tag,
self.matrice_coeffs_moy_gen,
) )
# Ajoute les moyennes par UEs + la moyenne générale (but) # Ajoute les moyennes par UEs + la moyenne générale (but)
moy_gen = self.compute_moy_gen() moy_gen = self.compute_moy_gen()
moy_res_gen = self.compute_moy_ues_tag(info_tag=None, pole=ModuleType.RESSOURCE)
moy_saes_gen = self.compute_moy_ues_tag(info_tag=None, pole=ModuleType.SAE)
self.moyennes_tags["but"] = pe_moytag.MoyennesTag( self.moyennes_tags["but"] = pe_moytag.MoyennesTag(
"but", "but",
pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_UE,
moy_gen, moy_gen,
self.matrice_coeffs_moy_gen, # , moy_gen_but moy_res_gen,
moy_saes_gen,
self.matrice_coeffs_moy_gen,
) )
# Tous les tags
self.tags_sorted = self.get_all_significant_tags() self.tags_sorted = self.get_all_significant_tags()
"""Tags (personnalisés+compétences) par ordre alphabétique""" """Tags (personnalisés+compétences) par ordre alphabétique"""
@ -206,33 +244,58 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
capitalisations = capitalisations.sort_index(axis=1) 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(
"""Calcule la moyenne par UE des étudiants pour un tag, self, info_tag: dict[int, dict] = None, pole=None
) -> pd.DataFrame:
"""Calcule la moyenne par UE des étudiants pour un tag donné,
en ayant connaissance des informations sur le tag. en ayant connaissance des informations sur le tag.
info_tag détermine les modules pris en compte :
* si non `None`, seuls les modules rattachés au tag sont pris en compte
* si `None`, tous les modules (quelque soit leur rattachement au tag) sont pris
en compte (sert au calcul de la moyenne générale par ressource ou SAE)
`pole` détermine les modules pris en compte :
* si `pole` vaut `ModuleType.RESSOURCE`, seules les ressources sont prises
en compte (moyenne de ressources par UEs)
* si `pole` vaut `ModuleType.SAE`, seules les SAEs sont prises en compte
* si `pole` vaut `None` (ou toute autre valeur),
tous les modules sont pris en compte (moyenne d'UEs)
Les informations sur le tag sont un dictionnaire listant les modimpl_id rattachés au tag, Les informations sur le tag sont un dictionnaire listant les modimpl_id rattachés au tag,
et pour chacun leur éventuel coefficient de **repondération**. et pour chacun leur éventuel coefficient de **repondération**.
Returns: Returns:
Le dataframe des moyennes du tag par UE Le dataframe des moyennes du tag par UE
""" """
modimpls_sorted = self.formsemestre.modimpls_sorted
# Adaptation du mask de calcul des moyennes au tag visé # Adaptation du mask de calcul des moyennes au tag visé
modimpls_mask = [ modimpls_mask = []
modimpl.module.ue.type == sco_codes.UE_STANDARD for modimpl in modimpls_sorted:
for modimpl in self.formsemestre.modimpls_sorted module = modimpl.module # Le module
] mask = module.ue.type == sco_codes.UE_STANDARD # Est-ce une UE stantard ?
if pole == ModuleType.RESSOURCE:
mask &= module.module_type == ModuleType.RESSOURCE
elif pole == ModuleType.SAE:
mask &= module.module_type == ModuleType.SAE
modimpls_mask += [mask]
# Désactive tous les modules qui ne sont pas pris en compte pour ce tag # Prise en compte du tag
for i, modimpl in enumerate(self.formsemestre.modimpls_sorted): if info_tag:
if modimpl.moduleimpl_id not in info_tag: # Désactive tous les modules qui ne sont pas pris en compte pour ce tag
modimpls_mask[i] = False for i, modimpl in enumerate(modimpls_sorted):
if modimpl.moduleimpl_id not in info_tag:
modimpls_mask[i] = False
# Applique la pondération des coefficients # Applique la pondération des coefficients
modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy() modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy()
for modimpl_id in info_tag: if info_tag:
ponderation = info_tag[modimpl_id]["ponderation"] for modimpl_id in info_tag:
modimpl_coefs_ponderes_df[modimpl_id] *= ponderation ponderation = info_tag[modimpl_id]["ponderation"]
modimpl_coefs_ponderes_df[modimpl_id] *= ponderation
# Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues) # Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)
moyennes_ues_tag = comp.moy_ue.compute_ue_moys_apc( moyennes_ues_tag = comp.moy_ue.compute_ue_moys_apc(
@ -304,24 +367,6 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
dict_tags["auto"] = {"but": {}} dict_tags["auto"] = {"but": {}}
return dict_tags return dict_tags
def _aff_ue_et_comp_debug(self):
"""Affichage pour debug"""
aff_comp = []
for acro in self.acronymes_sorted:
aff_comp += [f"📍{acro} (∈ 💡{self.acronymes_ues_to_competences[acro]})"]
pe_affichage.pe_print(f"--> UEs/Compétences : {', '.join(aff_comp)}")
def _aff_tags_debug(self, dict_tags):
"""Affichage pour debug"""
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
aff_tags_perso = ", ".join([f"👜{nom}" for nom in noms_tags_perso])
# Affichage
pe_affichage.pe_print(
f"""--> Tags du programme de formation : {aff_tags_perso} + Automatiques : {aff_tags_auto}"""
)
def _check_tags(self, dict_tags): def _check_tags(self, dict_tags):
"""Vérifie l'unicité des tags""" """Vérifie l'unicité des tags"""
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys()))) noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
@ -347,29 +392,6 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
""" """
raise ScoValueError(message) raise ScoValueError(message)
def __aff_profil_coeffs(self):
"""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 des coeffs d'UE (pour debug)
profils = []
for i in self.matrice_coeffs_moy_gen.index:
val = self.matrice_coeffs_moy_gen.loc[i].fillna("-")
val = " | ".join([str(v) for v in val])
if val not in profils:
profils += [val]
# L'affichage
if len(profils) > 1:
profils_aff = "\n" + "\n".join([" " * 10 + prof for prof in profils])
else:
profils_aff = "\n".join(profils)
pe_affichage.pe_print(
f"--> Moyenne générale calculée avec pour coeffs d'UEs : {profils_aff}"
)
def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre): def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
"""Etant données les implémentations des modules du semestre (modimpls), """Etant données les implémentations des modules du semestre (modimpls),
@ -414,12 +436,7 @@ def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
# Ajout du module (modimpl) au tagname considéré # Ajout du module (modimpl) au tagname considéré
synthese_tags[tagname][modimpl_id] = { synthese_tags[tagname][modimpl_id] = {
"modimpl": modimpl, # les données sur le module "modimpl": modimpl, # les données sur le module
# "coeff": modimpl.module.coefficient, # le coeff du module dans le semestre
"ponderation": ponderation, # la pondération demandée pour le tag sur le module "ponderation": ponderation, # la pondération demandée pour le tag sur le module
# "module_code": modimpl.module.code, # le code qui doit se retrouver à l'identique dans des ue capitalisee
# "ue_id": modimpl.module.ue.id, # les données sur l'ue
# "ue_code": modimpl.module.ue.ue_code,
# "ue_acronyme": modimpl.module.ue.acronyme,
} }
return synthese_tags return synthese_tags

View File

@ -43,6 +43,7 @@ import numpy as np
from app.pe.moys import pe_moytag, pe_tabletags from app.pe.moys import pe_moytag, pe_tabletags
import app.pe.rcss.pe_trajectoires as pe_trajectoires import app.pe.rcss.pe_trajectoires as pe_trajectoires
from app.scodoc.sco_utils import ModuleType
class SxTag(pe_tabletags.TableTag): class SxTag(pe_tabletags.TableTag):
@ -91,7 +92,8 @@ class SxTag(pe_tabletags.TableTag):
and isinstance(self.sxtag_id[1], int) and isinstance(self.sxtag_id[1], int)
), "Format de l'identifiant du SxTag non respecté" ), "Format de l'identifiant du SxTag non respecté"
self.nom_rcs = sxtag_id[0] self.agregat = sxtag_id[0]
"""Nom de l'aggrégat du RCS"""
self.semx = semx self.semx = semx
"""Le SemX sur lequel il s'appuie""" """Le SemX sur lequel il s'appuie"""
@ -121,7 +123,7 @@ class SxTag(pe_tabletags.TableTag):
# Les tags # Les tags
self.tags_sorted = self.ressembuttag_final.tags_sorted self.tags_sorted = self.ressembuttag_final.tags_sorted
"""Tags (extraits du ReSemBUTTag final)""" """Tags (extraits du ReSemBUTTag final)"""
aff_tag = ["👜" + tag for tag in self.tags_sorted] aff_tag = pe_affichage.repr_tags(self.tags_sorted)
pe_affichage.pe_print(f"--> Tags : {', '.join(aff_tag)}") pe_affichage.pe_print(f"--> Tags : {', '.join(aff_tag)}")
# Les UE données par leur acronyme # Les UE données par leur acronyme
@ -135,13 +137,18 @@ class SxTag(pe_tabletags.TableTag):
"""L'association acronyme d'UEs -> compétence""" """L'association acronyme d'UEs -> compétence"""
self.competences_sorted = sorted(self.acronymes_ues_to_competences.values()) self.competences_sorted = sorted(self.acronymes_ues_to_competences.values())
"""Les compétences triées par nom""" """Les compétences triées par nom"""
self._aff_ue_et_comp_debug()
aff = pe_affichage.repr_asso_ue_comp(self.acronymes_ues_to_competences)
pe_affichage.pe_print(f"--> UEs/Compétences : {aff}")
# Les coeffs pour la moyenne générale (traduisant également l'inscription # Les coeffs pour la moyenne générale (traduisant également l'inscription
# des étudiants aux UEs) (etudids_sorted x acronymes_ues_sorted) # des étudiants aux UEs) (etudids_sorted x acronymes_ues_sorted)
self.matrice_coeffs_moy_gen = self.ressembuttag_final.matrice_coeffs_moy_gen self.matrice_coeffs_moy_gen = self.ressembuttag_final.matrice_coeffs_moy_gen
"""La matrice des coeffs pour la moyenne générale""" """La matrice des coeffs pour la moyenne générale"""
self.__aff_profil_coeffs() aff = pe_affichage.repr_profil_coeffs(self.matrice_coeffs_moy_gen)
pe_affichage.pe_print(
f"--> Moyenne générale calculée avec pour coeffs d'UEs : {aff}"
)
# Masque des inscriptions et des capitalisations # Masque des inscriptions et des capitalisations
self.masque_df = None self.masque_df = None
@ -152,83 +159,85 @@ class SxTag(pe_tabletags.TableTag):
self.ressembuttags, self.ressembuttags,
self.fid_final, self.fid_final,
) )
self._aff_capitalisations() pe_affichage.aff_capitalisations(
self.etuds,
self.ressembuttags,
self.fid_final,
self.acronymes_sorted,
self.masque_df,
)
# Les moyennes par tag # Les moyennes par tag
self.moyennes_tags: dict[str, pd.DataFrame] = {} self.moyennes_tags: dict[str, pd.DataFrame] = {}
"""Moyennes aux UEs (identifiées par leur acronyme) des différents tags""" """Moyennes aux UEs (identifiées par leur acronyme) des différents tags"""
if self.tags_sorted: if self.tags_sorted:
pe_affichage.pe_print("--> Calcul des moyennes par tags :") pe_affichage.pe_print("--> Calcul des moyennes par tags :")
for tag in self.tags_sorted: for tag in self.tags_sorted:
# Y-a-t-il des notes ? pe_affichage.pe_print(f" > MoyTag 👜{tag}")
if not self.has_notes(tag):
pe_affichage.pe_print(f" > MoyTag 👜{tag} actuellement sans notes") # Masque des inscriptions aux UEs (extraits de la matrice de coefficients)
inscr_mask: np.array = ~np.isnan(self.matrice_coeffs_moy_gen.to_numpy())
# Moyennes (tous modules confondus)
if not self.has_notes_tag(tag):
pe_affichage.pe_print(
f" --> Semestre (final) actuellement sans notes"
)
matrice_moys_ues = pd.DataFrame( matrice_moys_ues = pd.DataFrame(
np.nan, index=self.etudids_sorted, columns=self.acronymes_sorted np.nan, index=self.etudids_sorted, columns=self.acronymes_sorted
) )
matrice_moys_res = pd.DataFrame(
np.nan, index=self.etudids_sorted, columns=self.acronymes_sorted
)
matrice_moys_saes = pd.DataFrame(
np.nan, index=self.etudids_sorted, columns=self.acronymes_sorted
)
else: else:
# Cube de note etudids x UEs # Moyennes tous modules confondus
notes_df, notes_cube = compute_notes_ues_cube( ### Cube de note etudids x UEs tous modules confondus
tag, notes_df_gen, notes_cube_gen = self.compute_notes_ues_cube(
self.etudids_sorted, tag, mode=None
self.acronymes_sorted, )
self.ressembuttags, # DataFrame des moyennes (tous modules confondus)
matrice_moys_ues = self.compute_notes_ues(
notes_cube_gen, masque_cube, inscr_mask
) )
# Masque des inscriptions aux UEs (extraits de la matrice de coefficients) ### Moyennes par ressources
inscr_mask: np.array = ~np.isnan(self.matrice_coeffs_moy_gen.to_numpy()) notes_df_res, notes_cube_res = self.compute_notes_ues_cube(
tag, mode=ModuleType.RESSOURCE
# Matrice des moyennes )
matrice_moys_ues: pd.DataFrame = compute_notes_ues( matrice_moys_res = self.compute_notes_ues(
notes_cube, notes_cube_res, masque_cube, inscr_mask
masque_cube,
self.etudids_sorted,
self.acronymes_sorted,
inscr_mask,
) )
# Affichage de debug ### Moyennes par SAEs
aff = pe_affichage.aff_profil_coeffs( notes_df_saes, notes_cube_saes = self.compute_notes_ues_cube(
self.matrice_coeffs_moy_gen, with_index=True tag, mode=ModuleType.SAE
)
matrice_moys_saes = self.compute_notes_ues(
notes_cube_saes, masque_cube, inscr_mask
) )
pe_affichage.pe_print(f" > MoyTag 👜{tag} : {aff}")
# Mémorise les infos pour la moyennes au tag # Mémorise les infos pour la moyennes au tag
self.moyennes_tags[tag] = pe_moytag.MoyennesTag( self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag, tag,
pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_UE,
matrice_moys_ues, matrice_moys_ues,
matrice_moys_res,
matrice_moys_saes,
self.matrice_coeffs_moy_gen, self.matrice_coeffs_moy_gen,
) )
def __aff_profil_coeff_ects(self, tag): # Affichage de debug
"""Extrait de la matrice des coeffs, les différents types d'inscription aff = pe_affichage.repr_profil_coeffs(
et de coefficients (appelés profil) des étudiants et les affiche self.matrice_coeffs_moy_gen, with_index=True
(pour debug) )
""" pe_affichage.pe_print(f" > Moyenne générale calculée avec : {aff}")
# Les profils des coeffs d'UE (pour debug) def has_notes_tag(self, tag):
profils = []
for i in self.matrice_coeffs_moy_gen.index:
val = self.matrice_coeffs_moy_gen.loc[i].fillna("-")
val = " | ".join([str(v) for v in val])
if val not in profils:
profils += [val]
# L'affichage
if len(profils) > 1:
profils_aff = "\n" + "\n".join([" " * 10 + prof for prof in profils])
else:
profils_aff = "\n".join(profils)
# L'affichage
ues = ", ".join(self.acronymes_sorted)
pe_affichage.pe_print(
f" > MoyTag 👜{tag} pour UEs : {ues} avec pour coeffs : {profils_aff}"
)
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.
Si oui, n'a pas (encore) de notes dans le resformsemestre final. Si oui, n'a pas (encore) de notes dans le resformsemestre final.
@ -239,13 +248,7 @@ class SxTag(pe_tabletags.TableTag):
True si a des notes, False sinon True si a des notes, False sinon
""" """
moy_tag_dernier_sem = self.ressembuttag_final.moyennes_tags[tag] moy_tag_dernier_sem = self.ressembuttag_final.moyennes_tags[tag]
notes = moy_tag_dernier_sem.matrice_notes return moy_tag_dernier_sem.has_notes(None)
nbre_nan = notes.isna().sum().sum()
nbre_notes_potentielles = len(notes.index) * len(notes.columns)
if nbre_nan == nbre_notes_potentielles:
return False
else:
return True
def __eq__(self, other): def __eq__(self, other):
"""Egalité de 2 SxTag sur la base de leur identifiant""" """Egalité de 2 SxTag sur la base de leur identifiant"""
@ -258,104 +261,128 @@ class SxTag(pe_tabletags.TableTag):
return f"SXTag basé sur {self.semx.get_repr()}" return f"SXTag basé sur {self.semx.get_repr()}"
else: else:
# affichage = [str(fid) for fid in self.ressembuttags] # affichage = [str(fid) for fid in self.ressembuttags]
return f"SXTag {self.nom_rcs}#{self.fid_final}" return f"SXTag {self.agregat}#{self.fid_final}"
def _aff_ue_et_comp_debug(self): def compute_notes_ues_cube(self, tag, mode=None) -> (pd.DataFrame, np.array):
"""Affichage pour debug""" """Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé)
aff_comp = [] nécessaire au calcul des moyennes du tag pour le RCS Sx.
for acro in self.acronymes_sorted: (Renvoie également le dataframe associé pour debug).
aff_comp += [f"📍{acro} (∈ 💡{self.acronymes_ues_to_competences[acro]})"]
pe_affichage.pe_print(f"--> UEs/Compétences : {', '.join(aff_comp)}")
def _aff_capitalisations(self): `mode` détermine les modules pris en compte :
"""Affichage des capitalisations du sxtag pour debug"""
aff_cap = []
for etud in self.etuds:
cap = []
for frmsem_id in self.ressembuttags:
if frmsem_id != self.fid_final:
for accr in self.acronymes_sorted:
if self.masque_df[frmsem_id].loc[etud.etudid, accr] > 0.0:
cap += [accr]
if cap:
aff_cap += [f" > {etud.nomprenom} : {', '.join(cap)}"]
if aff_cap:
pe_affichage.pe_print(f"--> ⚠️ Capitalisations :")
pe_affichage.pe_print("\n".join(aff_cap))
def __aff_profil_coeffs(self): * si `mode` vaut `ModuleType.RESSOURCE`, seules les ressources sont prises
"""Extrait de la matrice des coeffs, les différents types d'inscription en compte (moyenne de ressources par UEs)
et de coefficients (appelés profil) des étudiants et les affiche * si `mode` vaut `ModuleType.SAE`, seules les SAEs sont prises en compte
(pour debug) * si `mode` vaut `None` (ou toute autre valeur),
tous les modules sont pris en compte (moyenne d'UEs)
Args:
tag: Le tag considéré (personalisé ou "but")
""" """
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
# etudids_sorted = etudids_sorted
# acronymes_ues = sorted([ue.acronyme for ue in selMf.ues.values()])
semestres_id = list(self.ressembuttags.keys())
# Les profils des coeffs d'UE (pour debug) dfs = {}
profils = []
for i in self.matrice_coeffs_moy_gen.index:
val = self.matrice_coeffs_moy_gen.loc[i].fillna("-")
val = " | ".join([str(v) for v in val])
if val not in profils:
profils += [val]
# L'affichage for frmsem_id in semestres_id:
if len(profils) > 1: # Partant d'un dataframe vierge
profils_aff = "\n" + "\n".join([" " * 10 + prof for prof in profils]) df = pd.DataFrame(
else: np.nan, index=self.etudids_sorted, columns=self.acronymes_sorted
profils_aff = "\n".join(profils) )
pe_affichage.pe_print(
f"--> Moyenne générale calculée avec pour coeffs d'UEs : {profils_aff}" # Charge les notes du semestre tag
sem_tag = self.ressembuttags[frmsem_id]
moys_tag = sem_tag.moyennes_tags[tag]
if mode == ModuleType.RESSOURCE:
notes = moys_tag.matrice_notes_res
elif mode == ModuleType.SAE:
notes = moys_tag.matrice_notes_saes
else:
notes = moys_tag.matrice_notes_gen # dataframe etudids x ues
# les étudiants et les acronymes communs
etudids_communs, acronymes_communs = pe_comp.find_index_and_columns_communs(
df, notes
)
# Recopie
df.loc[etudids_communs, acronymes_communs] = notes.loc[
etudids_communs, acronymes_communs
]
# Supprime tout ce qui n'est pas numérique
for col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce")
# Stocke le df
dfs[frmsem_id] = df
"""Réunit les notes sous forme d'un cube etudids x ues x semestres"""
semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs]
etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1)
return dfs, etudids_x_ues_x_semestres
def compute_notes_ues(
self,
set_cube: np.array,
masque_cube: np.array,
inscr_mask: np.array,
) -> pd.DataFrame:
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
par UE) obtenue par un étudiant à un semestre.
Args:
set_cube: notes moyennes aux modules ndarray
(semestre_ids x etudids x UEs), des floats avec des NaN
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
inscr_mask: masque etudids x UE traduisant les inscriptions des
étudiants aux UE (du semestre terminal)
Returns:
Un DataFrame avec pour columns les moyennes par ues,
et pour rows les etudid
"""
# etudids_sorted: liste des étudiants (dim. 0 du cube) trié par etudid
# acronymes_sorted: liste des acronymes des ues (dim. 1 du cube) trié par acronyme
nb_etuds, nb_ues, nb_semestres = set_cube.shape
nb_etuds_mask, nb_ues_mask = inscr_mask.shape
# assert nb_etuds == len(self.etudids_sorted)
# assert nb_ues == len(self.acronymes_sorted)
# assert nb_etuds == nb_etuds_mask
# assert nb_ues == nb_ues_mask
# Entrées à garder dans le cube en fonction du masque d'inscription aux UEs du parcours
inscr_mask_3D = np.stack([inscr_mask] * nb_semestres, axis=-1)
set_cube = set_cube * inscr_mask_3D
# Entrées à garder en fonction des UEs capitalisées ou non
set_cube = set_cube * masque_cube
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
# Enlève les NaN du cube pour les entrées manquantes : NaN -> -1.0
set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0)
# Les moyennes par ues
# TODO: Pour l'instant un max sans prise en compte des UE capitalisées
etud_moy = np.max(set_cube_no_nan, axis=2)
# Fix les max non calculé -1 -> NaN
etud_moy[etud_moy < 0] = np.NaN
# Le dataFrame
etud_moy_tag_df = pd.DataFrame(
etud_moy,
index=self.etudids_sorted, # les etudids
columns=self.acronymes_sorted, # les acronymes d'UEs
) )
etud_moy_tag_df = etud_moy_tag_df.fillna(np.nan)
def compute_notes_ues_cube( return etud_moy_tag_df
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.
(Renvoie également le dataframe associé pour debug).
Args:
etudids_sorted: La liste des etudids triés par ordre croissant (dim 0)
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)
# etudids_sorted = etudids_sorted
# acronymes_ues = sorted([ue.acronyme for ue in selMf.ues.values()])
semestres_id = list(ressembuttags.keys())
dfs = {}
for frmsem_id in semestres_id:
# Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids_sorted, columns=acronymes_sorted)
# Charge les notes du semestre tag
sem_tag = ressembuttags[frmsem_id]
moys_tag = sem_tag.moyennes_tags[tag]
notes = moys_tag.matrice_notes # dataframe etudids x ues
# les étudiants et les acronymes communs
etudids_communs, acronymes_communs = pe_comp.find_index_and_columns_communs(
df, notes
)
# Recopie
df.loc[etudids_communs, acronymes_communs] = notes.loc[
etudids_communs, acronymes_communs
]
# Supprime tout ce qui n'est pas numérique
for col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce")
# Stocke le df
dfs[frmsem_id] = df
"""Réunit les notes sous forme d'un cube etudids x ues x semestres"""
semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs]
etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1)
return dfs, etudids_x_ues_x_semestres
def compute_masques_capitalisation_cube( def compute_masques_capitalisation_cube(
@ -415,65 +442,3 @@ def compute_masques_capitalisation_cube(
semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs] semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs]
etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1) etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1)
return dfs, etudids_x_ues_x_semestres return dfs, etudids_x_ues_x_semestres
def compute_notes_ues(
set_cube: np.array,
masque_cube: np.array,
etudids_sorted: list,
acronymes_sorted: list,
inscr_mask: np.array,
):
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
par UE) obtenue par un étudiant à un semestre.
Args:
set_cube: notes moyennes aux modules ndarray
(semestre_ids x etudids x UEs), des floats avec des NaN
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_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:
Un DataFrame avec pour columns les moyennes par ues,
et pour rows les etudid
"""
nb_etuds, nb_ues, nb_semestres = set_cube.shape
nb_etuds_mask, nb_ues_mask = inscr_mask.shape
assert nb_etuds == len(etudids_sorted)
assert nb_ues == len(acronymes_sorted)
assert nb_etuds == nb_etuds_mask
assert nb_ues == nb_ues_mask
# Entrées à garder dans le cube en fonction du masque d'inscription aux UEs du parcours
inscr_mask_3D = np.stack([inscr_mask] * nb_semestres, axis=-1)
set_cube = set_cube * inscr_mask_3D
# Entrées à garder en fonction des UEs capitalisées ou non
set_cube = set_cube * masque_cube
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
# Enlève les NaN du cube pour les entrées manquantes : NaN -> -1.0
set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0)
# Les moyennes par ues
# TODO: Pour l'instant un max sans prise en compte des UE capitalisées
etud_moy = np.max(set_cube_no_nan, axis=2)
# Fix les max non calculé -1 -> NaN
etud_moy[etud_moy < 0] = np.NaN
# Le dataFrame
etud_moy_tag_df = pd.DataFrame(
etud_moy,
index=etudids_sorted, # les etudids
columns=acronymes_sorted, # les acronymes d'UEs
)
etud_moy_tag_df = etud_moy_tag_df.fillna(np.nan)
return etud_moy_tag_df

View File

@ -1,4 +1,4 @@
# -*- mode: python -*- # -*- pole: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################## ##############################################################################
@ -79,12 +79,17 @@ class TableTag(object):
tag: str = "" tag: str = ""
moytag: pe_moytag.MoyennesTag = None moytag: pe_moytag.MoyennesTag = None
for tag, moytag in self.moyennes_tags.items(): for tag, moytag in self.moyennes_tags.items():
if moytag.has_notes(): if moytag.has_notes(None):
tags.append(tag) tags.append(tag)
return sorted(tags) return sorted(tags)
def to_df( def to_df(
self, administratif=True, aggregat=None, tags_cibles=None, cohorte=None self,
pole,
administratif=True,
aggregat=None,
tags_cibles=None,
cohorte=None,
) -> pd.DataFrame: ) -> pd.DataFrame:
"""Renvoie un dataframe listant toutes les données """Renvoie un dataframe listant toutes les données
des moyennes/classements/nb_inscrits/min/max/moy des moyennes/classements/nb_inscrits/min/max/moy
@ -98,6 +103,7 @@ class TableTag(object):
aggregat: l'aggrégat représenté aggregat: l'aggrégat représenté
tags_cibles: la liste des tags ciblés tags_cibles: la liste des tags ciblés
cohorte: la cohorte représentée cohorte: la cohorte représentée
pole: Les modules à prendre en compte dans la moyenne (None=tous, Ressources ou SAEs)
Returns: Returns:
Le dataframe complet de synthèse Le dataframe complet de synthèse
""" """
@ -114,20 +120,22 @@ class TableTag(object):
# Les étudiants visés # Les étudiants visés
if administratif: if administratif:
df = df_administratif(self.etuds, aggregat, cohorte) df = df_administratif(self.etuds, aggregat=aggregat, cohorte=cohorte)
else: else:
df = pd.DataFrame(index=self.etudids) df = pd.DataFrame(index=self.etudids)
# Ajout des données par tags # Ajout des données par tags
for tag in tags_cibles: for tag in tags_cibles:
if tag in self.moyennes_tags: if tag in self.moyennes_tags:
moy_tag_df = self.moyennes_tags[tag].to_df(aggregat, cohorte) moy_tag_df = self.moyennes_tags[tag].to_df(
pole, aggregat=aggregat, cohorte=cohorte
)
df = df.join(moy_tag_df) df = df.join(moy_tag_df)
# Tri par nom, prénom # Tri par nom, prénom
if administratif: if administratif:
colonnes_tries = [ colonnes_tries = [
_get_champ_administratif(champ, aggregat, cohorte) _get_champ_administratif(champ, aggregat=aggregat, cohorte=cohorte)
for champ in CHAMPS_ADMINISTRATIFS[1:] for champ in CHAMPS_ADMINISTRATIFS[1:]
] # Nom + Prénom ] # Nom + Prénom
df = df.sort_values(by=colonnes_tries) df = df.sort_values(by=colonnes_tries)
@ -155,6 +163,7 @@ def _get_champ_administratif(champ, aggregat=None, cohorte=None):
liste = [] liste = []
if aggregat != None: if aggregat != None:
liste += [aggregat] liste += [aggregat]
liste += [""] # le pole (None, RESSOURCES, SAEs)
liste += ["Administratif", "Identité"] liste += ["Administratif", "Identité"]
if cohorte != None: if cohorte != None:
liste += [champ] liste += [champ]

View File

@ -8,8 +8,9 @@
from flask import g from flask import g
from app import log from app import log
from app.pe.rcss import pe_rcs
PE_DEBUG = False PE_DEBUG = True
# On stocke les logs PE dans g.scodoc_pe_log # On stocke les logs PE dans g.scodoc_pe_log
@ -20,7 +21,7 @@ def pe_start_log() -> list[str]:
return g.scodoc_pe_log return g.scodoc_pe_log
def pe_print(*a): def pe_print(*a, **cles):
"Log (or print in PE_DEBUG mode) and store in g" "Log (or print in PE_DEBUG mode) and store in g"
if PE_DEBUG: if PE_DEBUG:
msg = " ".join(a) msg = " ".join(a)
@ -31,7 +32,8 @@ def pe_print(*a):
lines = pe_start_log() lines = pe_start_log()
msg = " ".join(a) msg = " ".join(a)
lines.append(msg) lines.append(msg)
log(msg) if "info" in cles:
log(msg)
def pe_get_log() -> str: def pe_get_log() -> str:
@ -43,7 +45,7 @@ def pe_get_log() -> str:
SANS_NOTE = "-" SANS_NOTE = "-"
def aff_profil_coeffs(matrice_coeffs_moy_gen, with_index=False): def repr_profil_coeffs(matrice_coeffs_moy_gen, with_index=False):
"""Affiche les différents types de coefficients (appelés profil) """Affiche les différents types de coefficients (appelés profil)
d'une matrice_coeffs_moy_gen (pour debug) d'une matrice_coeffs_moy_gen (pour debug)
""" """
@ -79,8 +81,20 @@ def aff_profil_coeffs(matrice_coeffs_moy_gen, with_index=False):
return profils_aff return profils_aff
def repr_asso_ue_comp(acronymes_ues_to_competences):
"""Représentation textuelle de l'association UE -> Compétences
fournies dans acronymes_ues_to_competences
"""
champs = acronymes_ues_to_competences.keys()
champs = sorted(champs)
aff_comp = []
for acro in champs:
aff_comp += [f"📍{acro} (∈ 💡{acronymes_ues_to_competences[acro]})"]
return ", ".join(aff_comp)
def aff_UEs(champs): def aff_UEs(champs):
"""Affiche les UEs""" """Représentation textuelle des UEs fournies dans `champs`"""
champs_tries = sorted(champs) champs_tries = sorted(champs)
aff_comp = [] aff_comp = []
@ -99,8 +113,92 @@ def aff_competences(champs):
return ", ".join(aff_comp) return ", ".join(aff_comp)
def aff_tag(tags): def repr_tags(tags):
"""Affiche les tags""" """Affiche les tags"""
tags_tries = sorted(tags) tags_tries = sorted(tags)
aff_tag = ["👜" + tag for tag in tags_tries] aff_tag = ["👜" + tag for tag in tags_tries]
return ", ".join(aff_tag) return ", ".join(aff_tag)
def aff_tags_par_categories(dict_tags):
"""Etant donné un dictionnaire de tags, triés
par catégorie (ici "personnalisés" ou "auto")
représentation textuelle des tags
"""
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
aff_tags_perso = ", ".join([f"👜{nom}" for nom in noms_tags_perso])
# Affichage
return f"Tags du programme de formation : {aff_tags_perso} + Automatiques : {aff_tags_auto}"
def aff_trajectoires_suivies_par_etudiants(etudiants):
"""Affiche les trajectoires (regroupement de (form)semestres)
amenant un étudiant du S1 à un semestre final"""
# Affichage pour debug
etudiants_ids = etudiants.etudiants_ids
jeunes = list(enumerate(etudiants_ids))
for no_etud, etudid in jeunes:
etat = "" if etudid in etudiants.abandons_ids else ""
pe_print(f"--> {etat} {etudiants.identites[etudid].nomprenom} (#{etudid}) :")
trajectoires = etudiants.trajectoires[etudid]
for nom_rcs, rcs in trajectoires.items():
if rcs:
pe_print(f" > RCS ⏯️{nom_rcs}: {rcs.get_repr()}")
def aff_semXs_suivis_par_etudiants(etudiants):
"""Affiche les SemX (regroupement de semestres de type Sx)
amenant un étudiant à valider un Sx"""
etudiants_ids = etudiants.etudiants_ids
jeunes = list(enumerate(etudiants_ids))
for no_etud, etudid in jeunes:
etat = "" if etudid in etudiants.abandons_ids else ""
pe_print(f"--> {etat} {etudiants.identites[etudid].nomprenom} :")
for nom_rcs, rcs in etudiants.semXs[etudid].items():
if rcs:
pe_print(f" > SemX ⏯️{nom_rcs}: {rcs.get_repr()}")
vides = []
for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES:
les_semX_suivis = []
for no_etud, etudid in jeunes:
if etudiants.semXs[etudid][nom_rcs]:
les_semX_suivis.append(etudiants.semXs[etudid][nom_rcs])
if not les_semX_suivis:
vides += [nom_rcs]
vides = sorted(list(set(vides)))
pe_print(f"⚠️ SemX sans données : {', '.join(vides)}")
def aff_capitalisations(etuds, ressembuttags, fid_final, acronymes_sorted, masque_df):
"""Affichage des capitalisations du sxtag pour debug"""
aff_cap = []
for etud in etuds:
cap = []
for frmsem_id in ressembuttags:
if frmsem_id != fid_final:
for accr in acronymes_sorted:
if masque_df[frmsem_id].loc[etud.etudid, accr] > 0.0:
cap += [accr]
if cap:
aff_cap += [f" > {etud.nomprenom} : {', '.join(cap)}"]
if aff_cap:
pe_print(f"--> ⚠️ Capitalisations :")
pe_print("\n".join(aff_cap))
def repr_comp_et_ues(acronymes_ues_to_competences):
"""Affichage pour debug"""
aff_comp = []
competences_sorted = sorted(acronymes_ues_to_competences.keys())
for comp in competences_sorted:
liste = []
for acro in acronymes_ues_to_competences:
if acronymes_ues_to_competences[acro] == comp:
liste += ["📍" + acro]
aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"]
return "\n".join(aff_comp)

View File

@ -40,10 +40,10 @@ import pandas as pd
from app import ScoValueError from app import ScoValueError
from app.models import FormSemestre, Identite, Formation from app.models import FormSemestre, Identite, Formation
from app.pe import pe_comp, pe_affichage from app.pe import pe_comp, pe_affichage
from app.pe.rcss import pe_rcs
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.comp.res_sem import load_formsemestre_results from app.comp.res_sem import load_formsemestre_results
import warnings
class EtudiantsJuryPE: class EtudiantsJuryPE:
@ -57,16 +57,17 @@ class EtudiantsJuryPE:
self.annee_diplome = annee_diplome self.annee_diplome = annee_diplome
"""L'année du diplôme""" """L'année du diplôme"""
self.identites: dict[int, Identite] = {} # ex. ETUDINFO_DICT self.identites: dict[int:Identite] = {} # ex. ETUDINFO_DICT
"Les identités des étudiants traités pour le jury" """Les identités des étudiants traités pour le jury"""
self.cursus: dict[int, dict] = {} self.cursus: dict[int:dict] = {}
"Les cursus (semestres suivis, abandons) des étudiants" """Les cursus (semestres suivis, abandons) des étudiants"""
self.cursus = {} self.trajectoires: dict[int:dict] = {}
"""Les trajectoires/chemins de semestres suivis par les étudiants """Les trajectoires (regroupement cohérents de semestres) suivis par les étudiants"""
pour atteindre un aggrégat donné
(par ex: 3S=S1+S2+S3 à prendre en compte avec d'éventuels redoublements)""" self.semXs: dict[int:dict] = {}
"""Les semXs (RCS de type Sx) suivis par chaque étudiant"""
self.etudiants_diplomes = {} self.etudiants_diplomes = {}
"""Les identités des étudiants à considérer au jury (ceux qui seront effectivement """Les identités des étudiants à considérer au jury (ceux qui seront effectivement
@ -101,27 +102,26 @@ class EtudiantsJuryPE:
self.cosemestres = cosemestres self.cosemestres = cosemestres
pe_affichage.pe_print( pe_affichage.pe_print(
f"1) Recherche des cosemestres -> {len(cosemestres)} trouvés" f"1) Recherche des cosemestres -> {len(cosemestres)} trouvés", info=True
) )
pe_affichage.pe_print("2) Liste des étudiants dans les différents cosemestres")
self.etudiants_ids = get_etudiants_dans_semestres(cosemestres)
pe_affichage.pe_print( pe_affichage.pe_print(
f" => {len(self.etudiants_ids)} étudiants trouvés dans les cosemestres" "2) Liste des étudiants dans les différents cosemestres", info=True
)
etudiants_ids = get_etudiants_dans_semestres(cosemestres)
pe_affichage.pe_print(
f" => {len(etudiants_ids)} étudiants trouvés dans les cosemestres",
info=True,
) )
# Analyse des parcours étudiants pour déterminer leur année effective de diplome # Analyse des parcours étudiants pour déterminer leur année effective de diplome
# avec prise en compte des redoublements, des abandons, .... # avec prise en compte des redoublements, des abandons, ....
pe_affichage.pe_print("3) Analyse des parcours individuels des étudiants") pe_affichage.pe_print(
"3) Analyse des parcours individuels des étudiants", info=True
)
for etudid in self.etudiants_ids: # Ajoute une liste d'étudiants
self.identites[etudid] = Identite.get_etud(etudid) self.add_etudiants(etudiants_ids)
# Analyse son cursus
self.analyse_etat_etudiant(etudid, cosemestres)
# Analyse son parcours pour atteindre chaque semestre de la formation
self.structure_cursus_etudiant(etudid)
# Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris # Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris
self.etudiants_diplomes = self.get_etudiants_diplomes() self.etudiants_diplomes = self.get_etudiants_diplomes()
@ -136,9 +136,10 @@ class EtudiantsJuryPE:
# Les identifiants des étudiants ayant redoublés ou ayant abandonnés # Les identifiants des étudiants ayant redoublés ou ayant abandonnés
# Synthèse # Synthèse
pe_affichage.pe_print(f"4) Bilan") pe_affichage.pe_print(f"4) Bilan", info=True)
pe_affichage.pe_print( pe_affichage.pe_print(
f"--> {len(self.etudiants_diplomes)} étudiants à diplômer en {self.annee_diplome}" f"--> {len(self.etudiants_diplomes)} étudiants à diplômer en {self.annee_diplome}",
info=True,
) )
nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_diplomes) nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_diplomes)
assert nbre_abandons == len(self.abandons_ids) assert nbre_abandons == len(self.abandons_ids)
@ -147,6 +148,24 @@ class EtudiantsJuryPE:
f"--> {nbre_abandons} étudiants traités mais non diplômés (redoublement, réorientation, abandon)" f"--> {nbre_abandons} étudiants traités mais non diplômés (redoublement, réorientation, abandon)"
) )
def add_etudiants(self, etudiants_ids):
"""Ajoute une liste d'étudiants aux données du jury"""
nbre_etudiants_ajoutes = 0
for etudid in etudiants_ids:
if etudid not in self.identites:
nbre_etudiants_ajoutes += 1
# L'identité de l'étudiant
self.identites[etudid] = Identite.get_etud(etudid)
# Analyse son cursus
self.analyse_etat_etudiant(etudid, self.cosemestres)
# Analyse son parcours pour atteindre chaque semestre de la formation
self.structure_cursus_etudiant(etudid)
self.etudiants_ids = set(self.identites.keys())
return nbre_etudiants_ajoutes
def get_etudiants_diplomes(self) -> dict[int, Identite]: def get_etudiants_diplomes(self) -> dict[int, Identite]:
"""Identités des étudiants (sous forme d'un dictionnaire `{etudid: Identite(etudid)}` """Identités des étudiants (sous forme d'un dictionnaire `{etudid: Identite(etudid)}`
qui vont être à traiter au jury PE pour qui vont être à traiter au jury PE pour
@ -215,11 +234,19 @@ class EtudiantsJuryPE:
if formsemestre.formation.is_apc() if formsemestre.formation.is_apc()
} }
# Le parcours final
parcour = formsemestres[0].etuds_inscriptions[etudid].parcour
if parcour:
libelle = parcour.libelle
else:
libelle = None
self.cursus[etudid] = { self.cursus[etudid] = {
"etudid": etudid, # les infos sur l'étudiant "etudid": etudid, # les infos sur l'étudiant
"etat_civil": identite.etat_civil, # Ajout à la table jury "etat_civil": identite.etat_civil, # Ajout à la table jury
"nom": identite.nom, "nom": identite.nom,
"entree": formsemestres[-1].date_debut.year, # La date d'entrée à l'IUT "entree": formsemestres[-1].date_debut.year, # La date d'entrée à l'IUT
"parcours": libelle, # Le parcours final
"diplome": get_annee_diplome( "diplome": get_annee_diplome(
identite identite
), # Le date prévisionnelle de son diplôme ), # Le date prévisionnelle de son diplôme
@ -234,8 +261,7 @@ class EtudiantsJuryPE:
if self.cursus[etudid]["diplome"] == self.annee_diplome: if self.cursus[etudid]["diplome"] == self.annee_diplome:
# Est-il démissionnaire : charge son dernier semestre pour connaitre son état ? # Est-il démissionnaire : charge son dernier semestre pour connaitre son état ?
dernier_semes_etudiant = formsemestres[0] dernier_semes_etudiant = formsemestres[0]
with warnings.catch_warnings(): res = load_formsemestre_results(dernier_semes_etudiant)
res = load_formsemestre_results(dernier_semes_etudiant)
etud_etat = res.get_etud_etat(etudid) etud_etat = res.get_etud_etat(etudid)
if etud_etat == scu.DEMISSION: if etud_etat == scu.DEMISSION:
self.cursus[etudid]["abandon"] = True self.cursus[etudid]["abandon"] = True
@ -245,29 +271,9 @@ class EtudiantsJuryPE:
identite, cosemestres identite, cosemestres
) )
def get_semestres_significatifs(self, etudid: int): # Initialise ses trajectoires/SemX/RCSemX
"""Ensemble des semestres d'un étudiant, qui : self.trajectoires[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS}
self.semXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_SEMESTRES}
* l'amènent à être diplômé à l'année visée
* l'auraient amené à être diplômé à l'année visée s'il n'avait pas redoublé et sera donc
diplômé plus tard
Supprime les semestres qui conduisent à une diplomation postérieure à celle du jury visé.
Args:
etudid: L'identifiant d'un étudiant
Returns:
Un dictionnaire ``{fid: FormSemestre(fid)}`` dans lequel les semestres
amènent à une diplômation antérieur à celle de la diplômation visée par le jury jury
"""
semestres_etudiant = self.cursus[etudid]["formsemestres"]
semestres_significatifs = {}
for fid in semestres_etudiant:
semestre = semestres_etudiant[fid]
if pe_comp.get_annee_diplome_semestre(semestre) <= self.annee_diplome:
semestres_significatifs[fid] = semestre
return semestres_significatifs
def structure_cursus_etudiant(self, etudid: int): def structure_cursus_etudiant(self, etudid: int):
"""Structure les informations sur les semestres suivis par un """Structure les informations sur les semestres suivis par un
@ -278,7 +284,9 @@ class EtudiantsJuryPE:
le dernier semestre (en date) de numéro i qu'il a suivi (1 ou 0 si pas encore suivi). le dernier semestre (en date) de numéro i qu'il a suivi (1 ou 0 si pas encore suivi).
Ce semestre influera les interclassements par semestre dans la promo. Ce semestre influera les interclassements par semestre dans la promo.
""" """
semestres_significatifs = self.get_semestres_significatifs(etudid) semestres_significatifs = get_semestres_significatifs(
self.cursus[etudid]["formsemestres"], self.annee_diplome
)
# Tri des semestres par numéro de semestre # Tri des semestres par numéro de semestre
for i in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1): for i in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1):
@ -381,13 +389,35 @@ class EtudiantsJuryPE:
return df return df
def get_semestres_significatifs(formsemestres, annee_diplome):
"""Partant d'un ensemble de semestre, renvoie les semestres qui amèneraient les étudiants
à être diplômé à l'année visée, y compris s'ils n'avaient pas redoublé et seraient donc
diplômé plus tard.
De fait, supprime les semestres qui conduisent à une diplomation postérieure
à celle visée.
Args:
formsemestres: une liste de formsemestres
annee_diplome: l'année du diplôme visée
Returns:
Un dictionnaire ``{fid: FormSemestre(fid)}`` dans lequel les semestres
amènent à une diplômation antérieur à celle de la diplômation visée par le jury
"""
# semestres_etudiant = self.cursus[etudid]["formsemestres"]
semestres_significatifs = {}
for fid in formsemestres:
semestre = formsemestres[fid]
if pe_comp.get_annee_diplome_semestre(semestre) <= annee_diplome:
semestres_significatifs[fid] = semestre
return semestres_significatifs
def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set: def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set:
"""Ensemble d'identifiants des étudiants (identifiés via leur ``etudid``) """Ensemble d'identifiants des étudiants (identifiés via leur ``etudid``)
inscrits à l'un des semestres de la liste de ``semestres``. inscrits à l'un des semestres de la liste de ``semestres``.
Remarque : Les ``cosemestres`` sont généralement obtenus avec
``sco_formsemestre.do_formsemestre_list()``
Args: Args:
semestres: Un dictionnaire ``{fid: Formsemestre(fid)}`` donnant un semestres: Un dictionnaire ``{fid: Formsemestre(fid)}`` donnant un
ensemble d'identifiant de semestres ensemble d'identifiant de semestres

View File

@ -65,6 +65,7 @@ from app.pe.moys import (
pe_moytag, pe_moytag,
) )
import app.pe.pe_rcss_jury as pe_rcss_jury import app.pe.pe_rcss_jury as pe_rcss_jury
from app.scodoc.sco_utils import *
class JuryPE(object): class JuryPE(object):
@ -93,7 +94,7 @@ class JuryPE(object):
pe_affichage.pe_print( pe_affichage.pe_print(
f"""***********************************************************\n""" f"""***********************************************************\n"""
f"""*** Recherche des étudiants diplômés 🎓 en {self.diplome}\n""" f"""*** Recherche des étudiants diplômés 🎓 en {self.diplome}\n"""
f"""***********************************************************\n""" f"""***********************************************************"""
) )
# Les infos sur les étudiants # Les infos sur les étudiants
@ -102,7 +103,7 @@ class JuryPE(object):
self.etudiants.find_etudiants() self.etudiants.find_etudiants()
self.diplomes_ids = self.etudiants.diplomes_ids self.diplomes_ids = self.etudiants.diplomes_ids
self.rcss_jury = pe_rcss_jury.RCSsJuryPE(self.diplome) self.rcss_jury = pe_rcss_jury.RCSsJuryPE(self.diplome, self.etudiants)
"""Les informations sur les regroupements de semestres""" """Les informations sur les regroupements de semestres"""
self.zipdata = io.BytesIO() self.zipdata = io.BytesIO()
@ -168,7 +169,16 @@ class JuryPE(object):
self.ressembuttags = {} self.ressembuttags = {}
for frmsem_id, formsemestre in formsemestres.items(): for frmsem_id, formsemestre in formsemestres.items():
# Crée le semestre_tag et exécute les calculs de moyennes # Crée le semestre_tag et exécute les calculs de moyennes
self.ressembuttags[frmsem_id] = pe_ressemtag.ResSemBUTTag(formsemestre) ressembuttag = pe_ressemtag.ResSemBUTTag(formsemestre)
self.ressembuttags[frmsem_id] = ressembuttag
# Ajoute les étudiants découverts dans les ressembuttags aux données des étudiants
# nbre_etudiants_ajoutes = self.etudiants.add_etudiants(
# ressembuttag.etudids_sorted
# )
# if nbre_etudiants_ajoutes:
# pe_affichage.pe_print(
# f"--> Ajout de {nbre_etudiants_ajoutes} étudiants aux données du jury"
# )
# Intègre le bilan des semestres taggués au zip final # Intègre le bilan des semestres taggués au zip final
pe_affichage.pe_print(f"2) Bilan") pe_affichage.pe_print(f"2) Bilan")
@ -178,14 +188,19 @@ class JuryPE(object):
) as writer: ) as writer:
onglets = [] onglets = []
for res_sem_tag in self.ressembuttags.values(): for res_sem_tag in self.ressembuttags.values():
onglet = res_sem_tag.get_repr(verbose=True) for pole in [None, ModuleType.RESSOURCE, ModuleType.SAE]:
onglet = onglet.replace("Semestre ", "S") onglet = res_sem_tag.get_repr(verbose=True)
onglets += ["📊" + onglet] onglet = onglet.replace("Semestre ", "S")
df = res_sem_tag.to_df() if pole:
# Conversion colonnes en multiindex onglet += (
df = convert_colonnes_to_multiindex(df) " (res.)" if pole == ModuleType.RESSOURCE else " (saes)"
# écriture dans l'onglet )
df.to_excel(writer, onglet, index=True, header=True) onglets += ["📊" + onglet]
df = res_sem_tag.to_df(pole)
# Conversion colonnes en multiindex
df = convert_colonnes_to_multiindex(df)
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"--> Export excel de {', '.join(onglets)}") pe_affichage.pe_print(f"--> Export excel de {', '.join(onglets)}")
output.seek(0) output.seek(0)
@ -207,8 +222,8 @@ class JuryPE(object):
"***************************************************************************" "***************************************************************************"
) )
self.rcss_jury.cree_trajectoires(self.etudiants) self.rcss_jury.cree_trajectoires()
self.rcss_jury._aff_trajectoires(self.etudiants) pe_affichage.aff_trajectoires_suivies_par_etudiants(self.etudiants)
def _gen_semXs(self): def _gen_semXs(self):
"""Génère les SemXs (trajectoires/combinaisons de semestre de même rang x) """Génère les SemXs (trajectoires/combinaisons de semestre de même rang x)
@ -218,8 +233,8 @@ class JuryPE(object):
pe_affichage.pe_print( pe_affichage.pe_print(
"*** Génère les SemXs (RCS de même Sx donnant lieu à validation du semestre)" "*** Génère les SemXs (RCS de même Sx donnant lieu à validation du semestre)"
) )
self.rcss_jury.cree_semxs(self.etudiants) self.rcss_jury.cree_semxs()
self.rcss_jury._aff_semxs_suivis(self.etudiants) pe_affichage.aff_semXs_suivis_par_etudiants(self.etudiants)
def _gen_xls_sxtags(self, zipfile: ZipFile): def _gen_xls_sxtags(self, zipfile: ZipFile):
"""Génère les semestres taggués en s'appuyant sur les RCF de type Sx (pour """Génère les semestres taggués en s'appuyant sur les RCF de type Sx (pour
@ -248,14 +263,20 @@ class JuryPE(object):
) as writer: ) as writer:
onglets = [] onglets = []
for sxtag in self.sxtags.values(): for sxtag in self.sxtags.values():
onglet = sxtag.get_repr(verbose=False)
if sxtag.is_significatif(): if sxtag.is_significatif():
df = sxtag.to_df() for pole in [None, ModuleType.RESSOURCE, ModuleType.SAE]:
# Conversion colonnes en multiindex onglet = sxtag.get_repr(verbose=False)
df = convert_colonnes_to_multiindex(df) if pole:
onglets += ["📊" + onglet] onglet += (
# écriture dans l'onglet " (res.)" if pole == ModuleType.RESSOURCE else " (saes)"
df.to_excel(writer, onglet, index=True, header=True) )
onglets += ["📊" + onglet]
df = sxtag.to_df(pole)
# Conversion colonnes en multiindex
df = convert_colonnes_to_multiindex(df)
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"--> Export excel de {', '.join(onglets)}") pe_affichage.pe_print(f"--> Export excel de {', '.join(onglets)}")
output.seek(0) output.seek(0)
@ -320,14 +341,21 @@ class JuryPE(object):
) as writer: ) as writer:
onglets = [] onglets = []
for rcs_tag in self.rcsstags.values(): for rcs_tag in self.rcsstags.values():
onglet = rcs_tag.get_repr(verbose=False)
if rcs_tag.is_significatif(): if rcs_tag.is_significatif():
df = rcs_tag.to_df() for pole in [None, ModuleType.RESSOURCE, ModuleType.SAE]:
# Conversion colonnes en multiindex onglet = rcs_tag.get_repr(verbose=False)
df = convert_colonnes_to_multiindex(df) if pole:
onglets += ["📊" + onglet] onglet += (
# écriture dans l'onglet " (res.)" if pole == ModuleType.RESSOURCE else " (saes)"
df.to_excel(writer, onglet, index=True, header=True) )
onglets += ["📊" + onglet]
df = rcs_tag.to_df(pole)
# Conversion colonnes en multiindex
df = convert_colonnes_to_multiindex(df)
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)}") pe_affichage.pe_print(f"--> Export excel de {', '.join(onglets)}")
output.seek(0) output.seek(0)
@ -393,14 +421,22 @@ class JuryPE(object):
]: ]:
interclasstag = self.interclasstags[type_interclass] interclasstag = self.interclasstags[type_interclass]
for nom_rcs, interclass in interclasstag.items(): for nom_rcs, interclass in interclasstag.items():
onglet = interclass.get_repr()
if interclass.is_significatif(): if interclass.is_significatif():
df = interclass.to_df(cohorte="Promo") for pole in [None, ModuleType.RESSOURCE, ModuleType.SAE]:
# Conversion colonnes en multiindex onglet = interclass.get_repr()
df = convert_colonnes_to_multiindex(df) if pole:
onglets += [onglet] onglet += (
# écriture dans l'onglet " (res.)"
df.to_excel(writer, onglet, index=True, header=True) if pole == ModuleType.RESSOURCE
else " (saes)"
)
onglets += ["📊" + onglet]
df = interclass.to_df(pole, cohorte="Promo")
# Conversion colonnes en multiindex
df = convert_colonnes_to_multiindex(df)
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)}") pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0) output.seek(0)
@ -428,7 +464,10 @@ class JuryPE(object):
tags = self._do_tags_list(self.interclasstags) tags = self._do_tags_list(self.interclasstags)
for tag in tags: for tag in tags:
for type_moy in [pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_COMPETENCES]: for type_moy in [pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_COMPETENCES]:
self.synthese[(tag, type_moy)] = self.df_tag_type(tag, type_moy) for pole in [None, ModuleType.RESSOURCE, ModuleType.SAE]:
self.synthese[(tag, type_moy, pole)] = self.df_tag_type(
tag, type_moy, pole
)
# Export des données => mode 1 seule feuille -> supprimé # Export des données => mode 1 seule feuille -> supprimé
pe_affichage.pe_print("*** Export du jury de synthese par tags") pe_affichage.pe_print("*** Export du jury de synthese par tags")
@ -443,10 +482,18 @@ class JuryPE(object):
df_final = convert_colonnes_to_multiindex(df_final) df_final = convert_colonnes_to_multiindex(df_final)
# Nom de l'onglet # Nom de l'onglet
if isinstance(onglet, tuple): if isinstance(onglet, tuple):
if onglet[1] == pe_moytag.CODE_MOY_COMPETENCES: (repr, type_moy, pole) = onglet
nom_onglet = onglet[0][: 31 - 7] + " (Comp)" nom_onglet = onglet[0][: 31 - 11]
if type_moy == pe_moytag.CODE_MOY_COMPETENCES:
nom_onglet = nom_onglet + "(Comp"
else: else:
nom_onglet = onglet[0][: 31 - 5] + " (UE)" nom_onglet = nom_onglet + "(UE"
if pole and pole == ModuleType.RESSOURCE:
nom_onglet = nom_onglet + ",res)"
elif pole and pole == ModuleType.SAE:
nom_onglet = nom_onglet + ",saes)"
else:
nom_onglet = nom_onglet + ")"
else: else:
nom_onglet = onglet nom_onglet = onglet
onglets += [nom_onglet] onglets += [nom_onglet]
@ -515,7 +562,7 @@ class JuryPE(object):
# Méthodes pour la synthèse du juryPE # Méthodes pour la synthèse du juryPE
# ***************************************************************************************************************** # *****************************************************************************************************************
def df_tag_type(self, tag, type_moy): def df_tag_type(self, tag, type_moy, pole):
"""Génère le DataFrame synthétisant les moyennes/classements (groupe + """Génère le DataFrame synthétisant les moyennes/classements (groupe +
interclassement promo) pour tous les aggrégats prévus, en fonction interclassement promo) pour tous les aggrégats prévus, en fonction
du type (UEs ou Compétences) de données souhaitées, du type (UEs ou Compétences) de données souhaitées,
@ -551,7 +598,7 @@ class JuryPE(object):
if interclass.is_significatif(): if interclass.is_significatif():
# Le dataframe du classement sur le groupe # Le dataframe du classement sur le groupe
df_groupe = interclass.compute_df_synthese_moyennes_tag( df_groupe = interclass.compute_df_synthese_moyennes_tag(
tag, aggregat=aggregat, type_colonnes=False tag, pole, aggregat=aggregat, type_colonnes=False
) )
if not df_groupe.empty: if not df_groupe.empty:
aff_aggregat += [aggregat] aff_aggregat += [aggregat]
@ -559,6 +606,7 @@ class JuryPE(object):
# Le dataframe du classement sur la promo # Le dataframe du classement sur la promo
df_promo = interclass.to_df( df_promo = interclass.to_df(
pole,
administratif=False, administratif=False,
aggregat=aggregat, aggregat=aggregat,
tags_cibles=[tag], tags_cibles=[tag],
@ -571,8 +619,14 @@ class JuryPE(object):
if aff_aggregat: if aff_aggregat:
aff_aggregat = sorted(set(aff_aggregat)) aff_aggregat = sorted(set(aff_aggregat))
if pole and pole == ModuleType.RESSOURCE:
aff_pole = "et par ressources"
elif pole and pole == ModuleType.SAE:
aff_pole = "et par saes"
else:
aff_pole = "tous modules confondus"
pe_affichage.pe_print( pe_affichage.pe_print(
f" -> Synthèse de 👜{tag} par {type_moy} avec {', '.join(aff_aggregat)}" f" -> Synthèse de 👜{tag} par {type_moy} {aff_pole} avec {', '.join(aff_aggregat)}"
) )
else: else:
pe_affichage.pe_print(f" -> Synthèse du tag {tag} par {type_moy} : <vide>") pe_affichage.pe_print(f" -> Synthèse du tag {tag} par {type_moy} : <vide>")
@ -587,6 +641,8 @@ class JuryPE(object):
Returns: Returns:
Un tuple nom, prenom, html Un tuple nom, prenom, html
""" """
pole = None
etudiant = self.etudiants.identites[etudid] etudiant = self.etudiants.identites[etudid]
nom = etudiant.nom nom = etudiant.nom
prenom = etudiant.prenom # initial du prénom prenom = etudiant.prenom # initial du prénom
@ -608,57 +664,58 @@ class JuryPE(object):
tags = self._do_tags_list(self.interclasstags) tags = self._do_tags_list(self.interclasstags)
# Descripti
# Les données par UE # Les données par UE
moyennes = {} moyennes = {}
for tag in tags: for tag in tags:
moyennes[tag] = {} moyennes[tag] = {}
# Les données de synthèse # Les données de synthèse
df = self.synthese[(tag, pe_moytag.CODE_MOY_COMPETENCES)] df = self.synthese[(tag, pe_moytag.CODE_MOY_COMPETENCES, pole)]
for aggregat in pe_rcs.TOUS_LES_RCS: for aggregat in pe_rcs.TOUS_LES_RCS:
moyennes[tag][aggregat] = {} # moyennes[tag][aggregat] = {}
descr = pe_rcs.get_descr_rcs(aggregat)
moy = {}
est_significatif = False
for comp in competences + ["Général"]: for comp in competences + ["Général"]:
moyennes[tag][aggregat][comp] = { moy[comp] = {
"note": "", "note": "",
"rang_groupe": "", "rang_groupe": "",
"rang_promo": "", "rang_promo": "",
} }
colonne = pe_moytag.get_colonne_df( colonne = pe_moytag.get_colonne_df(
aggregat, tag, comp, "Groupe", "note" aggregat, pole, tag, comp, "Groupe", "note"
) )
if colonne in df.columns: if colonne in df.columns:
valeur = df.loc[etudid, colonne] valeur = df.loc[etudid, colonne]
if not np.isnan(valeur): if not np.isnan(valeur):
moyennes[tag][aggregat][comp]["note"] = round(valeur, 2) moy[comp]["note"] = round(valeur, 2)
est_significatif = True
colonne = pe_moytag.get_colonne_df( colonne = pe_moytag.get_colonne_df(
aggregat, tag, comp, "Groupe", "rang" aggregat, pole, tag, comp, "Groupe", "rang"
) )
if colonne in df.columns: if colonne in df.columns:
valeur = df.loc[etudid, colonne] valeur = df.loc[etudid, colonne]
if valeur and str(valeur) != "nan": if valeur and str(valeur) != "nan":
moyennes[tag][aggregat][comp]["rang_groupe"] = valeur moy[comp]["rang_groupe"] = valeur
colonne = pe_moytag.get_colonne_df( colonne = pe_moytag.get_colonne_df(
aggregat, tag, comp, "Promo", "rang" aggregat, pole, tag, comp, "Promo", "rang"
) )
if colonne in df.columns: if colonne in df.columns:
valeur = df.loc[etudid, colonne] valeur = df.loc[etudid, colonne]
if valeur and str(valeur) != "nan": if valeur and str(valeur) != "nan":
moyennes[tag][aggregat][comp]["rang_promo"] = valeur moy[comp]["rang_promo"] = valeur
if est_significatif:
moyennes[tag][descr] = moy
html = template.render( html = template.render(
nom=nom, nom=nom,
prenom=prenom, prenom=prenom,
pole="Moyennes calculées tous modules (ressources/SAEs) confondus",
colonnes_html=colonnes_html, colonnes_html=colonnes_html,
tags=tags, tags=tags,
moyennes=moyennes, moyennes=moyennes,
) )
# for onglet, df_synthese in self.synthese.items():
# if isinstance(onglet, tuple): # Les onglets autres que "administratif"
# tag = onglet[0]
# type_moy = onglet[1]
# colonnes = list(df_synthese.columns)
return (nom, prenom, html) return (nom, prenom, html)

View File

@ -14,10 +14,13 @@ class RCSsJuryPE:
annee_diplome: L'année de diplomation annee_diplome: L'année de diplomation
""" """
def __init__(self, annee_diplome: int): def __init__(self, annee_diplome: int, etudiants: pe_etudiant.EtudiantsJuryPE):
self.annee_diplome = annee_diplome self.annee_diplome = annee_diplome
"""Année de diplômation""" """Année de diplômation"""
self.etudiants = etudiants
"""Les étudiants recensés"""
self.trajectoires: dict[tuple(int, str) : pe_trajectoires.Trajectoire] = {} self.trajectoires: dict[tuple(int, str) : pe_trajectoires.Trajectoire] = {}
"""Ensemble des trajectoires recensées (regroupement de (form)semestres BUT)""" """Ensemble des trajectoires recensées (regroupement de (form)semestres BUT)"""
@ -41,7 +44,7 @@ class RCSsJuryPE:
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
son RCSemX : {etudid: {nom_RCS: RCSemX}}""" son RCSemX : {etudid: {nom_RCS: RCSemX}}"""
def cree_trajectoires(self, etudiants: pe_etudiant.EtudiantsJuryPE): def cree_trajectoires(self):
"""Créé toutes les trajectoires, au regard du cursus des étudiants """Créé toutes les trajectoires, au regard du cursus des étudiants
analysés + les mémorise dans les données de l'étudiant analysés + les mémorise dans les données de l'étudiant
@ -49,31 +52,28 @@ class RCSsJuryPE:
etudiants: Les étudiants à prendre en compte dans le Jury PE etudiants: Les étudiants à prendre en compte dans le Jury PE
""" """
tous_les_aggregats = ( tous_les_aggregats = pe_rcs.TOUS_LES_RCS
pe_rcs.TOUS_LES_SEMESTRES + pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
) for etudid in self.etudiants.cursus:
for etudid in etudiants.cursus: self.trajectoires_suivies[etudid] = self.etudiants.trajectoires[etudid]
self.trajectoires_suivies[etudid] = {
aggregat: None for aggregat in tous_les_aggregats
}
for nom_rcs in tous_les_aggregats: for nom_rcs in tous_les_aggregats:
# L'aggrégat considéré (par ex: 3S=S1+S2+S3), son nom de son semestre # L'aggrégat considéré (par ex: 3S=S1+S2+S3), son nom de son semestre
# terminal (par ex: S3) et son numéro (par ex: 3) # terminal (par ex: S3) et son numéro (par ex: 3)
noms_semestre_de_aggregat = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"] noms_semestres = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"]
nom_semestre_terminal = noms_semestre_de_aggregat[-1] nom_semestre_final = noms_semestres[-1]
for etudid in etudiants.cursus: for etudid in self.etudiants.cursus:
# Le formsemestre terminal (dernier en date) associé au # Le (ou les) semestre(s) marquant la fin du cursus de l'étudiant
# semestre marquant la fin de l'aggrégat sems_final = self.etudiants.cursus[etudid][nom_semestre_final]
# (par ex: son dernier S3 en date) if sems_final:
trajectoire = etudiants.cursus[etudid][nom_semestre_terminal] # Le formsemestre final (dernier en date) de l'étudiant,
if trajectoire: # marquant la fin de son aggrégat (par ex: son dernier S3 en date)
formsemestre_final = app.pe.pe_comp.get_dernier_semestre_en_date( formsemestre_final = app.pe.pe_comp.get_dernier_semestre_en_date(
trajectoire sems_final
) )
# Ajout ou récupération du RCS associé # Ajout (si nécessaire) et récupération du RCS associé
rcs_id = (nom_rcs, formsemestre_final.formsemestre_id) rcs_id = (nom_rcs, formsemestre_final.formsemestre_id)
if rcs_id not in self.trajectoires: if rcs_id not in self.trajectoires:
self.trajectoires[rcs_id] = pe_trajectoires.Trajectoire( self.trajectoires[rcs_id] = pe_trajectoires.Trajectoire(
@ -84,7 +84,7 @@ class RCSsJuryPE:
# La liste des semestres de l'étudiant à prendre en compte # La liste des semestres de l'étudiant à prendre en compte
# pour cette trajectoire # pour cette trajectoire
semestres_a_aggreger = get_rcs_etudiant( semestres_a_aggreger = get_rcs_etudiant(
etudiants.cursus[etudid], formsemestre_final, nom_rcs self.etudiants.cursus[etudid], formsemestre_final, nom_rcs
) )
# Ajout des semestres au RCS # Ajout des semestres au RCS
@ -92,64 +92,30 @@ class RCSsJuryPE:
# Mémorise le RCS suivi par l'étudiant # Mémorise le RCS suivi par l'étudiant
self.trajectoires_suivies[etudid][nom_rcs] = rcs self.trajectoires_suivies[etudid][nom_rcs] = rcs
self.etudiants.trajectoires[etudid][nom_rcs] = rcs
def _aff_trajectoires(self, etudiants: pe_etudiant.EtudiantsJuryPE): def cree_semxs(self):
"""Affiche les chemins trouvés pour debug""" """Créé les SemXs (trajectoires/combinaisons de semestre de même rang x),
# Affichage pour debug
jeunes = list(enumerate(self.trajectoires_suivies))
for no_etud, etudid in jeunes:
etat = "" if etudid in etudiants.abandons_ids else ""
pe_affichage.pe_print(
f"--> {etat} {etudiants.identites[etudid].nomprenom} (#{etudid}) :"
)
for nom_rcs, rcs in self.trajectoires_suivies[etudid].items():
if rcs:
pe_affichage.pe_print(f" > RCS ⏯️{nom_rcs}: {rcs.get_repr()}")
def cree_semxs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé les les SemXs (trajectoires/combinaisons de semestre de même rang x),
en ne conservant dans les trajectoires que les regroupements en ne conservant dans les trajectoires que les regroupements
de type Sx""" de type Sx"""
self.semXs = {} self.semXs = {}
for rcs_id, trajectoire in self.trajectoires.items(): for rcs_id, trajectoire in self.trajectoires.items():
if trajectoire and trajectoire.nom in pe_rcs.TOUS_LES_SEMESTRES: if trajectoire.nom in pe_rcs.TOUS_LES_SEMESTRES:
self.semXs[rcs_id] = pe_trajectoires.SemX(trajectoire) self.semXs[rcs_id] = pe_trajectoires.SemX(trajectoire)
# L'association (pour chaque étudiant entre chaque Sx et le SemX associé)
self.semXs_suivis = {} self.semXs_suivis = {}
for etudid in self.trajectoires_suivies: for etudid in self.etudiants.trajectoires:
self.semXs_suivis[etudid] = { self.semXs_suivis[etudid] = {
nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES agregat: None for agregat in pe_rcs.TOUS_LES_SEMESTRES
} }
for agregat in pe_rcs.TOUS_LES_SEMESTRES:
for nom_rcs, trajectoire in self.trajectoires_suivies[etudid].items(): trajectoire = self.etudiants.trajectoires[etudid][agregat]
if trajectoire and nom_rcs in pe_rcs.TOUS_LES_SEMESTRES: if trajectoire:
rcs_id = trajectoire.rcs_id rcs_id = trajectoire.rcs_id
self.semXs_suivis[etudid][nom_rcs] = self.semXs[rcs_id] semX = self.semXs[rcs_id]
self.semXs_suivis[etudid][agregat] = semX
def _aff_semxs_suivis(self, etudiants: pe_etudiant.EtudiantsJuryPE): self.etudiants.semXs[etudid][agregat] = semX
"""Affichage des SemX pour debug"""
jeunes = list(enumerate(self.semXs_suivis))
for no_etud, etudid in jeunes:
etat = "" if etudid in etudiants.abandons_ids else ""
pe_affichage.pe_print(
f"--> {etat} {etudiants.identites[etudid].nomprenom} :"
)
for nom_rcs, rcs in self.semXs_suivis[etudid].items():
if rcs:
pe_affichage.pe_print(f" > SemX ⏯️{nom_rcs}: {rcs.get_repr()}")
vides = []
for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES:
les_semX_suivis = []
for no_etud, etudid in jeunes:
if self.semXs_suivis[etudid][nom_rcs]:
les_semX_suivis.append(self.semXs_suivis[etudid][nom_rcs])
if not les_semX_suivis:
vides += [nom_rcs]
vides = sorted(list(set(vides)))
pe_affichage.pe_print(f"⚠️ SemX sans données : {', '.join(vides)}")
def cree_rcsemxs(self, etudiants: pe_etudiant.EtudiantsJuryPE): def cree_rcsemxs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé tous les RCSemXs, au regard du cursus des étudiants """Créé tous les RCSemXs, au regard du cursus des étudiants

View File

@ -4,19 +4,36 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PE de {{ nom }}</title> <title>PE de {{ nom }}</title>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"/> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"/>
<style> <style>
.flash { padding: 8px; color: white; border-radius: 15px; font-weight: bold; } .w3-badge{
.success { background-color: limegreen; } background-color:#000;
.error { background-color: lightcoral; } color:#fff;
display:inline-block;
padding-left:8px;
padding-right:8px;
text-align:center;
border-radius:50%;
font-size: 70%;
}
.w3-red{background-color: darkred;}
.w3-blue{background-color: darkblue;}
ul.legend li {list-style: none; }
</style> </style>
</head> </head>
<body> <body>
<main class="container"> <main class="container">
<h1>Résultats PE de {{prenom}} {{nom}}</h1>
<h1>Résultats PE de {{prenom}} {{nom}}</h1>
<h2>Légende</h2>
<ul class="legend">
<li><span class="w3-badge w3-red">../..</span>&nbsp;Classement par groupe</li>
<li><span class="w3-badge w3-blue">../..</span>&nbsp;Classement par promo</li>
</ul>
{{ pole }}
{% for tag in tags %} {% for tag in tags %}