forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -46,9 +46,12 @@ import app.pe.pe_comp as pe_comp
|
||||
from app.pe.moys import pe_tabletags, pe_moytag
|
||||
|
||||
|
||||
class RCSTag(pe_tabletags.TableTag):
|
||||
class RCSemXTag(pe_tabletags.TableTag):
|
||||
def __init__(
|
||||
self, rcsemx: pe_rcsemx.RCSemX, sxstags: dict[(str, int) : pe_sxtag.SxTag]
|
||||
self,
|
||||
rcsemx: pe_rcsemx.RCSemX,
|
||||
sxstags: dict[(str, int) : pe_sxtag.SxTag],
|
||||
semXs_suivis: dict[int, dict],
|
||||
):
|
||||
"""Calcule les moyennes par tag (orientées compétences)
|
||||
d'un regroupement de SxTag
|
||||
@ -59,14 +62,19 @@ class RCSTag(pe_tabletags.TableTag):
|
||||
Args:
|
||||
rcsemx: Le RCSemX (identifié par un nom et l'id de son semestre terminal)
|
||||
sxstags: Les données sur les SemX taggués
|
||||
semXs_suivis: Les données indiquant quels SXTags sont à prendre en compte
|
||||
pour chaque étudiant
|
||||
"""
|
||||
pe_tabletags.TableTag.__init__(self)
|
||||
|
||||
self.rcs_id: tuple(str, int) = rcsemx.rcs_id
|
||||
"""Identifiant du RCSTag (identique au RCSemX sur lequel il s'appuie)"""
|
||||
"""Identifiant du RCSemXTag (identique au RCSemX sur lequel il s'appuie)"""
|
||||
|
||||
self.rcsemx: pe_rcsemx.RCSemX = rcsemx
|
||||
"""RCSemX associé au RCSTag"""
|
||||
"""Le regroupement RCSemX associé au RCSemXTag"""
|
||||
|
||||
self.semXs_suivis = semXs_suivis
|
||||
"""Les semXs suivis par les étudiants"""
|
||||
|
||||
self.nom = self.get_repr()
|
||||
"""Représentation textuelle du RSCtag"""
|
||||
@ -80,20 +88,21 @@ class RCSTag(pe_tabletags.TableTag):
|
||||
# Affichage pour debug
|
||||
pe_affichage.pe_print(f"*** {self.get_repr(verbose=True)}")
|
||||
|
||||
# Les données aggrégés (RCRCF + SxTags
|
||||
# Les données aggrégés (RCRCF + SxTags)
|
||||
self.semXs_aggreges: dict[(str, int) : pe_rcsemx.RCSemX] = rcsemx.semXs_aggreges
|
||||
"""Les SemX aggrégés"""
|
||||
self.sxstags = {}
|
||||
self.sxstags_aggreges = {}
|
||||
"""Les SxTag associés aux SemX aggrégés"""
|
||||
try:
|
||||
for rcf_id in self.semXs_aggreges:
|
||||
self.sxstags[rcf_id] = sxstags[rcf_id]
|
||||
self.sxstags_aggreges[rcf_id] = sxstags[rcf_id]
|
||||
except:
|
||||
raise ValueError("Semestres SxTag manquants")
|
||||
self.sxtags_connus = sxstags # Tous les sxstags connus
|
||||
|
||||
# Les étudiants (etuds, états civils & etudis)
|
||||
sems_dans_aggregat = pe_rcs.TYPES_RCS[self.rcs_id[0]]["aggregat"]
|
||||
sxtag_final = self.sxstags[(sems_dans_aggregat[-1], self.rcs_id[1])]
|
||||
sems_dans_aggregat = rcsemx.aggregat
|
||||
sxtag_final = self.sxstags_aggreges[(sems_dans_aggregat[-1], self.rcs_id[1])]
|
||||
self.etuds = sxtag_final.etuds
|
||||
"""Les étudiants (extraits du semestre final)"""
|
||||
self.add_etuds(self.etuds)
|
||||
@ -123,28 +132,48 @@ class RCSTag(pe_tabletags.TableTag):
|
||||
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
|
||||
for tag in self.tags_sorted:
|
||||
pe_affichage.pe_print(f"--> Moyennes du tag 👜{tag}")
|
||||
|
||||
# Traitement des inscriptions aux semX(tags)
|
||||
# ******************************************
|
||||
# Cube d'inscription (etudids_sorted x compétences_sorted x sxstags)
|
||||
# indiquant quel sxtag est valide pour chaque étudiant
|
||||
inscriptions_df, inscriptions_cube = self.compute_inscriptions_comps_cube(
|
||||
tag, self.etudids_sorted, self.competences_sorted, self.sxstags_aggreges
|
||||
)
|
||||
|
||||
# Traitement des notes
|
||||
# ********************
|
||||
# Cube de notes (etudids_sorted x compétences_sorted x sxstags)
|
||||
notes_df, notes_cube = self.compute_notes_comps_cube(
|
||||
tag, self.etudids_sorted, self.competences_sorted, self.sxstags
|
||||
tag, self.etudids_sorted, self.competences_sorted, self.sxstags_aggreges
|
||||
)
|
||||
# Calcule des moyennes/coeffs sous forme d'un dataframe"""
|
||||
# Calcule les moyennes sous forme d'un dataframe en les "aggrégant"
|
||||
# compétence par compétence
|
||||
moys_competences = compute_notes_competences(
|
||||
notes_cube, self.etudids_sorted, self.competences_sorted
|
||||
notes_cube,
|
||||
inscriptions_cube,
|
||||
self.etudids_sorted,
|
||||
self.competences_sorted,
|
||||
)
|
||||
# Cube de coeffs pour la moyenne générale,
|
||||
# traduisant les inscriptions des étudiants aux UEs (etudids_sorted x compétences_sorted x sxstags)
|
||||
|
||||
# Traitement des coeffs pour la moyenne générale
|
||||
# ***********************************************
|
||||
# Df des coeffs sur tous les SxTags aggrégés
|
||||
coeffs_df, coeffs_cube = self.compute_coeffs_comps_cube(
|
||||
tag,
|
||||
self.etudids_sorted,
|
||||
self.competences_sorted,
|
||||
self.sxstags,
|
||||
self.sxstags_aggreges,
|
||||
)
|
||||
# Calcule la 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(
|
||||
coeffs_cube, notes_cube, self.etudids_sorted, self.competences_sorted
|
||||
coeffs_cube,
|
||||
inscriptions_cube,
|
||||
notes_cube,
|
||||
self.etudids_sorted,
|
||||
self.competences_sorted,
|
||||
)
|
||||
self.__aff_profil_coeffs(matrice_coeffs_moy_gen)
|
||||
pe_affichage.aff_profil_coeffs(matrice_coeffs_moy_gen)
|
||||
|
||||
# Mémorise les moyennes et les coeff associés
|
||||
self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
|
||||
@ -162,9 +191,11 @@ class RCSTag(pe_tabletags.TableTag):
|
||||
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
|
||||
est basée)"""
|
||||
if verbose:
|
||||
return self.rcsemx.get_repr(verbose=verbose)
|
||||
return f"{self.__class__.__name__} basé sur " + self.rcsemx.get_repr(
|
||||
verbose=verbose
|
||||
)
|
||||
else:
|
||||
return f"{self.__class__.__name__} ({self.rcs_id})"
|
||||
return f"{self.__class__.__name__} {self.rcs_id}"
|
||||
|
||||
def compute_notes_comps_cube(
|
||||
self,
|
||||
@ -287,6 +318,57 @@ class RCSTag(pe_tabletags.TableTag):
|
||||
|
||||
return coeffs_dfs, coeffs_etudids_x_comps_x_sxtag
|
||||
|
||||
def compute_inscriptions_comps_cube(
|
||||
self,
|
||||
tag,
|
||||
etudids_sorted: list[int],
|
||||
competences_sorted: list[str],
|
||||
sxstags: dict[(str, int) : pe_sxtag.SxTag],
|
||||
):
|
||||
"""Pour un tag donné, construit
|
||||
le cube etudid x competences x SxTag traduisant quels sxtags est à prendre
|
||||
en compte pour chaque étudiant.
|
||||
Contient des 0 et des 1 pour indiquer la prise en compte.
|
||||
|
||||
Args:
|
||||
tag: Le tag visé
|
||||
etudids_sorted: Les etudis triés
|
||||
competences_sorted: Les compétences triées
|
||||
sxstags: Les SxTag à réunir
|
||||
"""
|
||||
# Initialisation
|
||||
inscriptions_dfs = {}
|
||||
|
||||
for sxtag_id, sxtag in sxstags.items():
|
||||
# Partant d'un dataframe vierge
|
||||
inscription_df = pd.DataFrame(
|
||||
0, index=etudids_sorted, columns=competences_sorted
|
||||
)
|
||||
# Stocke les dfs
|
||||
inscriptions_dfs[sxtag_id] = inscription_df
|
||||
|
||||
for etudid in etudids_sorted:
|
||||
for sem in self.rcsemx.aggregat:
|
||||
if etudid in self.semXs_suivis:
|
||||
semx_suivi = self.semXs_suivis[etudid][sem]
|
||||
if semx_suivi:
|
||||
semx_suivi_id = semx_suivi.rcs_id
|
||||
if semx_suivi_id not in self.sxtags_connus:
|
||||
pe_affichage.pe_print(
|
||||
f"Un SxTag est manquant : {semx_suivi_id}"
|
||||
)
|
||||
if semx_suivi_id in inscriptions_dfs:
|
||||
# Si le sxtag est l'un des siens
|
||||
inscriptions_dfs[semx_suivi_id].loc[etudid, :] = 1
|
||||
|
||||
"""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]
|
||||
inscriptions_etudids_x_comps_x_sxtag = np.stack(
|
||||
sxtag_x_etudids_x_comps, axis=-1
|
||||
)
|
||||
|
||||
return inscriptions_dfs, inscriptions_etudids_x_comps_x_sxtag
|
||||
|
||||
def _do_taglist(self) -> list[str]:
|
||||
"""Synthétise les tags à partir des Sxtags aggrégés.
|
||||
|
||||
@ -294,8 +376,8 @@ class RCSTag(pe_tabletags.TableTag):
|
||||
Liste de tags triés par ordre alphabétique
|
||||
"""
|
||||
tags = []
|
||||
for frmsem_id in self.sxstags:
|
||||
tags.extend(self.sxstags[frmsem_id].tags_sorted)
|
||||
for frmsem_id in self.sxstags_aggreges:
|
||||
tags.extend(self.sxstags_aggreges[frmsem_id].tags_sorted)
|
||||
return sorted(set(tags))
|
||||
|
||||
def _do_acronymes_to_competences(self) -> dict[str:str]:
|
||||
@ -307,7 +389,7 @@ class RCSTag(pe_tabletags.TableTag):
|
||||
Un dictionnaire {'acronyme_ue' : 'compétences'}
|
||||
"""
|
||||
dict_competences = {}
|
||||
for sxtag_id, sxtag in self.sxstags.items():
|
||||
for sxtag_id, sxtag in self.sxstags_aggreges.items():
|
||||
dict_competences |= sxtag.acronymes_ues_to_competences
|
||||
return dict_competences
|
||||
|
||||
@ -324,60 +406,43 @@ class RCSTag(pe_tabletags.TableTag):
|
||||
pe_affichage.pe_print(f"--> Compétences :")
|
||||
pe_affichage.pe_print("\n".join(aff_comp))
|
||||
|
||||
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 compute_coeffs_competences(
|
||||
coeff_cube: np.array,
|
||||
inscriptions: np.array,
|
||||
set_cube: np.array,
|
||||
etudids_sorted: list,
|
||||
competences_sorted: list,
|
||||
):
|
||||
"""Calcule les coeffs à utiliser pour la moyenne générale (toutes compétences
|
||||
confondues), en fonction des notes (set_cube) aggrégées.
|
||||
confondues), en fonction des inscriptions.
|
||||
|
||||
Args:
|
||||
coeffs_cube: coeffs impliqués dans la moyenne générale (semestres par semestres)
|
||||
set_cube: notes moyennes aux modules ndarray
|
||||
(etuds x UEs|compétences x sxtags), des floats avec des NaN
|
||||
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
|
||||
competences_sorted: list (dim. 1 du cube)
|
||||
|
||||
Returns:
|
||||
Un DataFrame de coefficients (etudids_sorted x compétences_sorted)
|
||||
"""
|
||||
nb_etuds, nb_comps, nb_semestres = set_cube.shape
|
||||
nb_etuds, nb_comps, nb_semestres = inscriptions.shape
|
||||
assert nb_etuds == len(etudids_sorted)
|
||||
assert nb_comps == len(competences_sorted)
|
||||
|
||||
# Applique le masque des inscriptions aux coeffs et aux notes
|
||||
coeffs_significatifs = coeff_cube * inscriptions
|
||||
set_cube_significatif = set_cube * inscriptions
|
||||
|
||||
# Quelles entrées du cube contiennent des notes ?
|
||||
mask = ~np.isnan(set_cube)
|
||||
mask = ~np.isnan(set_cube_significatif)
|
||||
|
||||
# Enlève les NaN du cube de notes pour les entrées manquantes
|
||||
coeffs_cube_no_nan = np.nan_to_num(coeff_cube, nan=0.0)
|
||||
coeffs_cube_no_nan = np.nan_to_num(coeffs_significatifs, nan=0.0)
|
||||
|
||||
# Retire les coefficients associées à des données sans notes
|
||||
# Retire les coefficients associés à des données sans notes
|
||||
coeffs_cube_no_nan = coeffs_cube_no_nan * mask
|
||||
|
||||
# Somme les coefficients (correspondant à des notes)
|
||||
@ -395,6 +460,7 @@ def compute_coeffs_competences(
|
||||
|
||||
def compute_notes_competences(
|
||||
set_cube: np.array,
|
||||
inscriptions: np.array,
|
||||
etudids_sorted: list,
|
||||
competences_sorted: list,
|
||||
):
|
||||
@ -406,11 +472,12 @@ def compute_notes_competences(
|
||||
par aggrégat de plusieurs semestres.
|
||||
|
||||
Args:
|
||||
set_cube: notes moyennes aux modules ndarray
|
||||
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
|
||||
tags: liste des tags (dim. 1 du cube)
|
||||
competences_sorted: list (dim. 1 du cube)
|
||||
Returns:
|
||||
Un DataFrame avec pour columns les moyennes par tags,
|
||||
et pour rows les etudid
|
||||
@ -419,11 +486,14 @@ def compute_notes_competences(
|
||||
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)
|
||||
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, nan=0.0)
|
||||
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)
|
||||
|
@ -9,7 +9,7 @@
|
||||
from flask import g
|
||||
from app import log
|
||||
|
||||
PE_DEBUG = False
|
||||
PE_DEBUG = True
|
||||
|
||||
|
||||
# On stocke les logs PE dans g.scodoc_pe_log
|
||||
@ -41,3 +41,41 @@ def pe_get_log() -> str:
|
||||
|
||||
# Affichage dans le tableur pe en cas d'absence de notes
|
||||
SANS_NOTE = "-"
|
||||
|
||||
|
||||
def aff_profil_coeffs(matrice_coeffs_moy_gen, with_index=False):
|
||||
"""Affiche les différents types de coefficients (appelés profil)
|
||||
d'une matrice_coeffs_moy_gen (pour debug)
|
||||
"""
|
||||
|
||||
# Les profils des coeffs d'UE (pour debug)
|
||||
profils = []
|
||||
index_a_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]
|
||||
index_a_profils[val] = [str(i)]
|
||||
else:
|
||||
index_a_profils[val] += [str(i)]
|
||||
|
||||
# L'affichage
|
||||
if len(profils) > 1:
|
||||
if with_index:
|
||||
elmts = [
|
||||
" " * 10
|
||||
+ prof
|
||||
+ " (par ex. "
|
||||
+ ", ".join(index_a_profils[prof][:10])
|
||||
+ ")"
|
||||
for prof in profils
|
||||
]
|
||||
else:
|
||||
elmts = [" " * 10 + prof for prof in profils]
|
||||
profils_aff = "\n" + "\n".join(elmts)
|
||||
else:
|
||||
profils_aff = "\n".join(profils)
|
||||
pe_print(
|
||||
f" > Moyenne calculée avec pour coeffs (de compétences) : {profils_aff}"
|
||||
)
|
||||
|
@ -306,9 +306,11 @@ class JuryPE(object):
|
||||
)
|
||||
|
||||
pe_affichage.pe_print("1) Calcul des moyennes des RCSTag")
|
||||
self.rcss_tags = {}
|
||||
self.rcsstags = {}
|
||||
for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items():
|
||||
self.rcss_tags[rcs_id] = pe_rcstag.RCSTag(rcsemx, self.sxtags)
|
||||
self.rcsstags[rcs_id] = pe_rcstag.RCSemXTag(
|
||||
rcsemx, self.sxtags, self.rcss_jury.semXs_suivis
|
||||
)
|
||||
|
||||
# Intègre le bilan des trajectoires tagguées au zip final
|
||||
pe_affichage.pe_print("2) Bilan")
|
||||
@ -317,7 +319,7 @@ class JuryPE(object):
|
||||
output, engine="openpyxl"
|
||||
) as writer:
|
||||
onglets = []
|
||||
for rcs_tag in self.rcss_tags.values():
|
||||
for rcs_tag in self.rcsstags.values():
|
||||
onglet = rcs_tag.get_repr(verbose=False)
|
||||
if rcs_tag.is_significatif():
|
||||
df = rcs_tag.to_df()
|
||||
@ -374,7 +376,7 @@ class JuryPE(object):
|
||||
pe_moytag.CODE_MOY_COMPETENCES,
|
||||
etudiants_diplomes,
|
||||
self.rcss_jury.rcsemxs,
|
||||
self.rcss_tags,
|
||||
self.rcsstags,
|
||||
self.rcss_jury.rcsemxs_suivis,
|
||||
)
|
||||
self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][nom_rcs] = interclass
|
||||
|
@ -87,18 +87,21 @@ class RCS:
|
||||
tous se terminant par un (form)semestre final.
|
||||
"""
|
||||
|
||||
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
|
||||
self.nom: str = nom_rcs
|
||||
def __init__(self, nom: str, semestre_final: FormSemestre):
|
||||
self.nom: str = nom
|
||||
"""Nom du RCS"""
|
||||
assert self.nom in TOUS_LES_RCS, "Le nom d'un RCS doit être un aggrégat"
|
||||
|
||||
self.aggregat: list[str] = TYPES_RCS[nom]["aggregat"]
|
||||
"""Aggrégat (liste des nom des semestres aggrégés)"""
|
||||
|
||||
self.formsemestre_final: FormSemestre = semestre_final
|
||||
"""(Form)Semestre final du RCS"""
|
||||
|
||||
self.rang_final = self.formsemestre_final.semestre_id
|
||||
"""Rang du formsemestre final"""
|
||||
|
||||
self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id)
|
||||
self.rcs_id: (str, int) = (nom, semestre_final.formsemestre_id)
|
||||
"""Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)"""
|
||||
|
||||
self.fid_final: int = self.formsemestre_final.formsemestre_id
|
||||
|
@ -26,12 +26,12 @@ class RCSemX(pe_rcs.RCS):
|
||||
incluant des infos sur les redoublements).
|
||||
|
||||
Args:
|
||||
nom_rcs: Un nom du RCS (par ex: '5S')
|
||||
nom: Un nom du RCS (par ex: '5S')
|
||||
semestre_final: Le semestre final du RCS
|
||||
"""
|
||||
|
||||
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
|
||||
pe_rcs.RCS.__init__(self, nom_rcs, semestre_final)
|
||||
def __init__(self, nom: str, semestre_final: FormSemestre):
|
||||
pe_rcs.RCS.__init__(self, nom, semestre_final)
|
||||
|
||||
self.semXs_aggreges: dict[(str, int) : pe_sxtag.SxTag] = {}
|
||||
"""Les semX à aggréger"""
|
||||
|
@ -25,12 +25,12 @@ class Trajectoire(pe_rcs.RCS):
|
||||
* des S1+S2+(année de césure)+S3 si césure, ...
|
||||
|
||||
Args:
|
||||
nom_rcs: Un nom du RCS (par ex: '5S')
|
||||
nom: Un nom du RCS (par ex: '5S')
|
||||
semestre_final: Le formsemestre final du RCS
|
||||
"""
|
||||
|
||||
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
|
||||
pe_rcs.RCS.__init__(self, nom_rcs, semestre_final)
|
||||
def __init__(self, nom: str, semestre_final: FormSemestre):
|
||||
pe_rcs.RCS.__init__(self, nom, semestre_final)
|
||||
|
||||
self.semestres_aggreges: dict[int:FormSemestre] = {}
|
||||
"""Formsemestres regroupés dans le RCS"""
|
||||
|
Loading…
Reference in New Issue
Block a user