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
9 changed files with 501 additions and 315 deletions
Showing only changes of commit 68bd20f8de - Show all commits

View File

@ -680,18 +680,3 @@ def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str:
return " ".join(description) return " ".join(description)
def convert_trajectoire_to_sxtag_id(trajectoire: dict[int:FormSemestre]) -> (int, int):
"""Partant d'une trajectoire (dictionnaire de la forme {fid: FormSemestre}),
renvoie l'identifiant (rang_sem, fid du semestre_terminal) associé"""
if not trajectoire:
return None
rangs = [formsemestre.semestre_id for formsemestre in trajectoire.values()]
assert len(set(rangs)) == 1, "Les trajectoires doivent être de même rang"
rang = min(rangs)
fid_terminal = list(trajectoire.values())[0].formsemestre_id
for fid, formsemestre in trajectoire.items():
if trajectoire[fid_terminal].date_fin <= formsemestre.date_fin:
fid_terminal = fid
return (rang, fid_terminal)

View File

@ -98,7 +98,7 @@ class RCSInterclasseTag(TableTag):
"""Association entre chaque étudiant et la trajectoire tagguée à prendre en """Association entre chaque étudiant et la trajectoire tagguée à prendre en
compte pour l'aggrégat""" compte pour l'aggrégat"""
for etudid in self.diplomes_ids: for etudid in self.diplomes_ids:
self.suivi[etudid] = rcss_jury_pe.suivi[etudid][nom_rcs] self.suivi[etudid] = rcss_jury_pe.rcss_suivis[etudid][nom_rcs]
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)"""

View File

@ -94,6 +94,9 @@ 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_rcs.RCSsJuryPE(self.diplome)
"""Les informations sur les regroupements de semestres"""
self.zipdata = io.BytesIO() self.zipdata = io.BytesIO()
with ZipFile(self.zipdata, "w") as zipfile: with ZipFile(self.zipdata, "w") as zipfile:
if not self.diplomes_ids: if not self.diplomes_ids:
@ -102,7 +105,9 @@ class JuryPE(object):
self._gen_xls_diplomes(zipfile) self._gen_xls_diplomes(zipfile)
self._gen_xls_ressembuttags(zipfile) self._gen_xls_ressembuttags(zipfile)
self._gen_rcss()
self._gen_xls_sxtags(zipfile) self._gen_xls_sxtags(zipfile)
# self._gen_rcrcfs()
# self._gen_xls_rcss_tags(zipfile) # self._gen_xls_rcss_tags(zipfile)
# self._gen_xls_interclassements_rcss(zipfile) # self._gen_xls_interclassements_rcss(zipfile)
# self._gen_xls_synthese_jury_par_tag(zipfile) # self._gen_xls_synthese_jury_par_tag(zipfile)
@ -173,41 +178,36 @@ class JuryPE(object):
) )
def _gen_rcss(self): def _gen_rcss(self):
"""Génère les RCS (attribut `rcss_jury`), combinaisons de semestres suivis par les étudiants au sens """Génère les RCS (attribut `rcss_jury`), combinaisons de semestres
d'un nom de RCS (par ex: 'S2' ou '3S'). suivis par les étudiants au sens d'un nom de RCS (par ex: 'S2' ou '3S').
""" """
pe_affichage.pe_print( pe_affichage.pe_print(
"*** Génère les RCS (différentes combinaisons de semestres) des étudiants" "*** Génère les RCS (différentes combinaisons de semestres) des étudiants"
) )
self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome)
self.rcss_jury.cree_rcss(self.etudiants) self.rcss_jury.cree_rcss(self.etudiants)
self.rcss_jury.cree_rcfs(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 RCS de type Sx (pour """Génère les semestres taggués en s'appuyant sur les RCS de type Sx (pour
identifier les redoublements impactant les semestres taggués). identifier les redoublements impactant les semestres taggués).
""" """
# Génère les regroupements de semestres de type Sx
pe_affichage.pe_print("*** Génère les RCF (RCS de type Sx)***")
self.rcss_jury.cree_rcfs(self.etudiants)
# Génère les moyennes des RCS de type Sx # Génère les moyennes des RCS de type Sx
pe_affichage.pe_print("*** Calcule les moyennes des SxTag") pe_affichage.pe_print("*** Calcule les moyennes des SxTag")
# Les regroupements de Sx # Les SxTag (moyenne de Sx par UE)
self.regroupements = {}
for rang in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1):
self.regroupements[rang] = {}
for etudid in self.etudiants.etudiants_ids:
trajectoire = self.etudiants.trajectoires[etudid][f"S{rang}"]
if trajectoire:
sxtag_id = pe_etudiant.convert_trajectoire_to_sxtag_id(trajectoire)
if sxtag_id not in self.regroupements[rang]:
self.regroupements[rang][sxtag_id] = {}
else:
self.regroupements[rang][sxtag_id] |= trajectoire
# Les SxTag
self.sxtags = {} self.sxtags = {}
for rang in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1): for rcf_id, rcf in self.rcss_jury.rcfs.items():
for sxtag_id in self.regroupements[rang]: # SxTag traduisant le RCF
trajectoires = self.regroupements[rang][sxtag_id] sxtag_id = rcf_id
ressemstags = {fid: self.ressembuttags[fid] for fid in trajectoires} # Les resultats des semestres taggués à prendre en compte dans le RCF
ressemstags = {
fid: self.ressembuttags[fid] for fid in rcf.semestres_aggreges
}
self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, ressemstags) self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, ressemstags)
# Intègre le bilan des semestres taggués au zip final # Intègre le bilan des semestres taggués au zip final
@ -215,9 +215,9 @@ class JuryPE(object):
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl" output, engine="openpyxl"
) as writer: ) as writer:
for sem_tag in self.sxtags.values(): for sxtag in self.sxtags.values():
onglet = sem_tag.get_repr(verbose=False) onglet = sxtag.get_repr(verbose=False)
df = sem_tag.df_moyennes_et_classements() df = sxtag.df_moyennes_et_classements()
# écriture dans l'onglet # écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True) df.to_excel(writer, onglet, index=True, header=True)
output.seek(0) output.seek(0)
@ -229,9 +229,16 @@ class JuryPE(object):
path="details", path="details",
) )
def _gen_xls_rcss_tags(self, zipfile: ZipFile): def _gen_rcrcfs(self):
"""Génère les RCS taggués (autres que ceux de type Sx), etc... """Génère les regroupements cohérents de RCFs qu'ont suivi chaque étudiant"""
en calculant les moyennes et les classements par tag pour chaque RCS.
pe_affichage.pe_print("*** Génère les RCRCF (regroupements de RCF de type Sx) amenant du S1 à un semestre final***")
self.rcss_jury.cree_rcrcfs(self.etudiants)
def _gen_xls_rcrcss_tags(self, zipfile: ZipFile):
"""Génère les RCS taggués traduisant les moyennes (orientées compétences)
de regroupements de semestre de type Sx, xA ou xS.
Stocke le résultat dans self.rccs_tag, un dictionnaire de Stocke le résultat dans self.rccs_tag, un dictionnaire de
la forme ``{nom_aggregat: {fid_terminal: RCSTag(fid_terminal)} }`` la forme ``{nom_aggregat: {fid_terminal: RCSTag(fid_terminal)} }``
@ -249,12 +256,11 @@ class JuryPE(object):
""" """
# Génère les moyennes des RCS de type Sx # Génère les moyennes des RCS de type Sx
pe_affichage.pe_print("*** Calcule les moyennes des RCS") pe_affichage.pe_print("*** Calcule les moyennes des RC de RCFS")
self.rcss_tags = {} self.rcss_tags = {}
for rcs_id, rcs in self.rcss_jury.rcss.items(): for rcs_id, rcrcf in self.rcss_jury.rcrcfs.items():
# nom = rcs.get_repr() self.rcss_tags[rcs_id] = RCSTag(rcrcf, self.sxtags)
self.rcss_tags[rcs_id] = RCSTag(rcs, self.res_sems_tags)
# Intègre le bilan des trajectoires tagguées au zip final # Intègre le bilan des trajectoires tagguées au zip final
output = io.BytesIO() output = io.BytesIO()
@ -431,7 +437,7 @@ class JuryPE(object):
# considéré # considéré
trajectoires_tagguees = [] trajectoires_tagguees = []
for etudid in etudids: for etudid in etudids:
trajectoire = self.rcss_jury.suivi[etudid][aggregat] trajectoire = self.rcss_jury.rcss_suivis[etudid][aggregat]
if trajectoire: if trajectoire:
tid = trajectoire.sxtag_id tid = trajectoire.sxtag_id
trajectoire_tagguee = self.rcss_tags[tid] trajectoire_tagguee = self.rcss_tags[tid]
@ -577,7 +583,7 @@ class JuryPE(object):
) )
# La trajectoire de l'étudiant sur l'aggrégat # La trajectoire de l'étudiant sur l'aggrégat
trajectoire = self.rcss_jury.suivi[etudid][aggregat] trajectoire = self.rcss_jury.rcss_suivis[etudid][aggregat]
if trajectoire: if trajectoire:
trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id] trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id]
if tag in trajectoire_tagguee.moyennes_tags: if tag in trajectoire_tagguee.moyennes_tags:

View File

@ -155,101 +155,74 @@ class Moyenne:
return self.synthese["nb_inscrits"] > 0 return self.synthese["nb_inscrits"] > 0
class MoyennesTag: class MoyennesTag:
def __init__( def __init__(
self, self,
tag: str, tag: str,
ues: dict[int, UniteEns], matrice_notes: pd.DataFrame, # etudids x colonnes
notes_ues: pd.DataFrame, matrice_coeffs: pd.DataFrame # etudids x colonnes
ues_inscr_parcours_df: pd.DataFrame
# notes_gen: pd.Series,
): ):
"""Classe centralisant la synthèse des moyennes/classements d'une série """Classe centralisant la synthèse des moyennes/classements d'une série
d'étudiants à un tag donné, en différenciant les notes d'étudiants à un tag donné, en différenciant les notes
obtenues aux UE et au général (toutes UEs confondues) obtenues aux UE et au général (toutes UEs confondues)
Args: Args:
tag: Un tag tag: Un tag
ues: La liste des UEs ayant servie au calcul de la moyenne matrice_notes: Les moyennes (etudid x acronymes_ues ou etudid x compétences) aux différentes UEs ou compétences
notes_ues: Les moyennes (etudid x acronymes_ues) aux différentes UEs et pour le tag matrice_coeffs: Les coeff à appliquer pour le calcul de la moyenne générale
ues_inscr_parcours_df: Les inscriptions des etudid au UE
# notes_gen: Une série de notes (moyenne) sous forme d'un pd.Series() (toutes UEs confondues) # notes_gen: Une série de notes (moyenne) sous forme d'un pd.Series() (toutes UEs confondues)
""" """
self.tag = tag self.tag = tag
"""Le tag associé aux moyennes""" """Le tag associé aux moyennes"""
# Les UE
self.ues: dict[int, UniteEns] = ues
"""Les UEs sur lesquelles sont calculées les moyennes"""
colonnes = list(notes_ues.columns)
acronymes: list[str] = [self.ues[ue_id].acronyme for ue_id in self.ues]
assert len(set(acronymes)) == len(
colonnes
), "Deux UEs ne peuvent pas avoir le même acronyme"
# Les inscriptions des etudids aux UEs
self.ues_inscr_parcours_df: pd.DataFrame = ues_inscr_parcours_df
"""Les inscriptions des etudids au UE en fonction de leur parcours"""
# Les coefficients à appliquer aux UEs pour la moyenne générale = ECTS
self.ects = self.ues_inscr_parcours_df.fillna(0.0) * [
ue.ects
for ue in self.ues.values() # if ue.type != UE_SPORT <= déjà supprimé
]
# Les profils d'ects (pour debug)
profils_ects = []
for val in list(self.ects.values):
if tuple(val) not in profils_ects:
profils_ects.append(tuple(val))
# Les moyennes par UE # Les moyennes par UE
self.notes_ues: pd.DataFrame = notes_ues self.matrice_notes: pd.DataFrame = matrice_notes
"""Les notes aux UEs (dataframe)""" """Les notes aux UEs ou aux compétences (DataFrame)"""
self.notes_ues.columns = acronymes # remplace les ue.id par leur acronyme
self.moys_ues: dict[int, pd.DataFrame] = {} self.matrice_coeffs: pd.DataFrame = matrice_coeffs
"""Les coeffs à appliquer pour le calcul des moyennes générales
(toutes UE ou compétences confondues). NaN si étudiant non inscrit"""
self.moyennes: dict[int, pd.DataFrame] = {}
"""Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs""" """Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs"""
for ue in self.ues.values(): # if ue.type != UE_SPORT: colonnes = self.matrice_notes.columns
notes = notes_ues[ue.acronyme] for col in colonnes: # if ue.type != UE_SPORT:
self.moys_ues[ue.acronyme] = Moyenne(notes) notes = matrice_notes[col]
self.moyennes[col] = Moyenne(notes)
# Les moyennes générales # Les moyennes générales
notes_gen = self.compute_moy_gen(self.notes_ues, self.ects) notes_gen = self.compute_moy_gen(self.matrice_notes, self.matrice_coeffs)
self.notes_gen = notes_gen self.notes_gen = notes_gen
"""Les notes générales (moyenne toutes UEs confonudes)""" """Les notes générales (moyenne toutes UEs confonudes)"""
self.moy_gen = Moyenne(notes_gen) self.moyenne_gen = Moyenne(notes_gen)
"""Le dataframe retraçant les moyennes/classements/statistiques général""" """Le dataframe retraçant les moyennes/classements/statistiques général"""
pe_affichage.pe_print(f"> MoyTag pour {tag} avec")
pe_affichage.pe_print(f" - ues={acronymes}")
pe_affichage.pe_print(f" - ects={profils_ects}")
def __eq__(self, other): def compute_moy_gen(self, moys: pd.DataFrame, coeffs: pd.DataFrame) -> pd.Series:
"""Egalité de deux MoyenneTag lorsque leur tag sont identiques""" """Calcule la moyenne générale (toutes UE/compétences confondus)
return self.tag == other.tag
def compute_moy_gen(
self, moy_ues: pd.DataFrame, coeff_ues: pd.DataFrame
) -> pd.Series:
"""Calcule la moyenne générale (toutes UE confondus)
pour le tag considéré, en pondérant les notes obtenues au UE pour le tag considéré, en pondérant les notes obtenues au UE
par les crédits ECTS. par les coeff (généralement les crédits ECTS).
Args: Args:
moy_ues: Les moyennes etudids x acronymes_ues moys: Les moyennes etudids x acronymes_ues/compétences
coeff_ues: Les coeff etudids x ueids coeff: Les coeff etudids x ueids/compétences
""" """
# Calcule la moyenne générale dans le semestre (pondérée par le ECTS) # Calcule la moyenne générale dans le semestre (pondérée par le ECTS)
try: try:
moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects( moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects(
moy_ues, moys,
coeff_ues, coeffs.fillna(0.0),
# formation_id=self.formsemestre.formation_id, # formation_id=self.formsemestre.formation_id,
skip_empty_ues=True, skip_empty_ues=True,
) )
except TypeError as e: except TypeError as e:
raise TypeError("Pb dans le calcul de la moyenne toutes UEs confondues") raise TypeError(
"Pb dans le calcul de la moyenne toutes UEs/compétences confondues"
)
return moy_gen_tag return moy_gen_tag

View File

@ -12,7 +12,7 @@ Created on 01-2024
import app.pe.pe_comp as pe_comp import app.pe.pe_comp as pe_comp
from app.models import FormSemestre from app.models import FormSemestre
from app.pe import pe_sxtag from app.pe import pe_sxtag, pe_affichage
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
TYPES_RCS = { TYPES_RCS = {
@ -80,28 +80,14 @@ TOUS_LES_RCS = list(TYPES_RCS.keys())
TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")] TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")]
def get_descr_rcs(nom_rcs: str) -> str:
"""Renvoie la description pour les tableurs de synthèse
Excel d'un nom de RCS"""
return TYPES_RCS[nom_rcs]["descr"]
class RCS: class RCS:
"""Modélise un ensemble de semestres finals d'étudiants """Modélise un regroupement cohérent de semestres (formsemestre ou de Sx)"""
associé à un type de regroupement cohérent de semestres
donné (par ex: 'S2', '3S', '2A').
Si le RCS est un semestre de type Si, stocke le
formsemestres de numéro i qu'ont suivi l'étudiant pour atteindre le Si
(en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants)
Pour le RCS de type iS ou iA (par ex, 3A=S1+S2+S3), elle identifie
les semestres que les étudiants ont suivis pour les amener jusqu'au semestre
terminal de la trajectoire (par ex: ici un S3).
Ces semestres peuvent être :
* des S1+S2+S1+S2+S3 si redoublement de la 1ère année
* des S1+S2+(année de césure)+S3 si césure, ...
Args:
nom_rcs: Un nom du RCS (par ex: '5S')
semestre_final: Le semestre final du RCS
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre): def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
self.nom: str = nom_rcs self.nom: str = nom_rcs
@ -110,42 +96,155 @@ class RCS:
self.formsemestre_final: FormSemestre = semestre_final self.formsemestre_final: FormSemestre = semestre_final
"""FormSemestre terminal du RCS""" """FormSemestre terminal du RCS"""
self.rang_final = self.formsemestre_final.semestre_id
"""Le rang du formsemestre final"""
self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id) self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id)
"""Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)""" """Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)"""
# self.semestres_aggreges: dict[int:FormSemestre] = {} def get_formsemestre_id_final(self) -> int:
# """Semestres regroupés dans le RCS""" """Renvoie l'identifiant du formsemestre final du RCS
self.sxtags_aggreges: dict[(str, int): pe_sxtag.SxTag] = {} Returns:
"""Les SxTag aggrégés""" L'id du formsemestre final (marquant la fin) du RCS
"""
def get_formsemestre_id_final(self):
"""Renvoie l'identifiant du formsemestre final du RCS"""
return self.formsemestre_final.formsemestre_id return self.formsemestre_final.formsemestre_id
def add_sxtags_a_aggreger(self, sxtags: dict[(str,int): pe_sxtag.SxTag]): def __repr__(self):
"""Ajout des SxTag aux semestres à regrouper """Représentation textuelle d'un RCS"""
return f"{self.nom} ({self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"
def get_repr(self):
return self.__repr__()
def __eq(self, other):
"""Egalité de RCS"""
return (
self.nom == other.nom
and self.formsemestre_final == other.formsemestre_final
)
class RCF(RCS):
"""Modélise un ensemble de (form)semestres d'étudiants
associé à un type de regroupement cohérent de semestres
donné (par ex: 'S2', '3S', '2A').
Si le RCF est un semestre de type Si, stocke les
formsemestres de numéro i qu'ont suivi l'étudiant pour atteindre le Si
(en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants)
Pour le RCF de type iS ou iA (par ex, 3A=S1+S2+S3), identifie
les semestres que les étudiants ont suivis pour les amener jusqu'au semestre
terminal du RCS (par ex: ici un S3).
Ces semestres peuvent être :
* des S1+S2+S1+S2+S3 si redoublement de la 1ère année
* des S1+S2+(année de césure)+S3 si césure, ...
Args: Args:
sxtags: Dictionnaire ``{(str,fid): SxTag}`` à ajouter nom_rcs: Un nom du RCS (par ex: '5S')
semestre_final: Le formsemestre final du RCS
""" """
self.sxtags_aggreges = self.sxtags_aggreges | sxtags
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
RCS.__init__(self, nom_rcs, semestre_final)
self.semestres_aggreges: dict[int:FormSemestre] = {}
"""Formsemestres regroupés dans le RCS"""
def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]):
"""Ajout de semestres aux semestres à regrouper
Args:
semestres: Dictionnaire ``{fid: Formsemestre)``
"""
self.semestres_aggreges = self.semestres_aggreges | semestres
def get_repr(self, verbose=True) -> str: def get_repr(self, verbose=True) -> str:
"""Représentation textuelle d'un RCS """Représentation textuelle d'un RCS
basé sur ses sxtags aggrégés""" basé sur ses semestres aggrégés"""
noms = [] noms = []
for sxtag_id, sxtag in self.sxtags_aggreges.items(): for fid in self.semestres_aggreges:
noms.append(f"S{sxtag.semestre_id}") semestre = self.semestres_aggreges[fid]
noms.append(f"S{semestre.semestre_id}({fid})")
noms = sorted(noms) noms = sorted(noms)
title = f"""{self.nom} ({ title = f"""{str(self)}"""
self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"""
if verbose and noms: if verbose and noms:
title += " - " + "+".join(noms) title += " - " + "+".join(noms)
return title return title
def get_rcf_from_semestres_aggreges(
rcfs: dict[(str, int):RCF], semestres_a_aggreges: list[FormSemestre]
) -> (str, int):
"""Partant d'un dictionnaire de RCFs (de la forme
``{ (nom_rcs, fid): RCF }, et connaissant une liste
de (form)semestres à aggréger, renvoie l'identifiant
(nom_rcs, fid) du RCFs qui lui correspond (c'est à dire celui dont
les semestres_aggregés par le RCF sont les même que les
semestres_a_aggreger.
Returns:
rcf_id: L'identifiant du RCF trouvé
"""
fids_semestres_a_aggreger = set(
[frms.formsemestre_id for frms in semestres_a_aggreges]
)
for rcf_id, rcf in rcfs.items():
fids_rcf = set(rcf.semestres_aggreges)
if fids_rcf == fids_semestres_a_aggreger:
return rcf_id
return None
class RCRCF:
"""Modélise les RCF d'étudiants suivis par un étudiant dans
le cadre d'un RCS donné (par ex: 3S=S1+S2+S3).
Pour rappel : un RCF (par ex. S1) combine les semestres 1 qu'a suivi
l'étudiant pour valider son S1 (1 si étudiant standard, 2 si redoublant).
Le RCRCF 3S est donc le regroupement du RCF S1 + RCF S2 + RCF S3.
Il est identifié par le formsemestre de S3 marquant la fin du regroupement.
Args:
nom_rcs: Un nom du RCS (par ex: '5S')
semestre_final: Le semestre final du RCS
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
RCS.__init__(self, nom_rcs, semestre_final)
self.rcfs_aggreges: dict[(str, int) : pe_sxtag.SxTag] = {}
"""Les RCFs à aggréger"""
def add_rcfs_a_aggreger(self, rcfs: dict[(str, int):RCF]):
"""Ajout des RCFs aux RCFS à regrouper
Args:
rcfs: Dictionnaire ``{(str,fid): RCF}`` à ajouter
"""
self.rcfs_aggreges = self.rcfs_aggreges | rcfs
def get_repr(self, verbose=True) -> str:
"""Représentation textuelle d'un RCSF
basé sur ses RCF aggrégés"""
noms = []
for rcf_id, rcf in self.rcfs_aggreges.items():
noms.append(rcf.get_repr())
title = f"""{str(self)}"""
if verbose and noms:
title += " : " + "+".join(noms)
return title
class RCSsJuryPE: class RCSsJuryPE:
"""Classe centralisant tous les regroupements cohérents de """Classe centralisant tous les regroupements cohérents de
semestres (RCS) des étudiants à prendre en compte dans un jury PE semestres (RCS) des étudiants à prendre en compte dans un jury PE
@ -158,78 +257,151 @@ class RCSsJuryPE:
self.annee_diplome = annee_diplome self.annee_diplome = annee_diplome
"""Année de diplômation""" """Année de diplômation"""
self.rcss: dict[tuple:RCS] = {} self.rcss: dict[tuple(int, str) : RCF] = {}
"""Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCS}""" """Ensemble des RCS recensés"""
self.suivi: dict[int:str] = {} self.rcss_suivis: dict[int:dict] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS, """Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
son RCS : {etudid: {nom_RCS: RCS}}""" son RCS : {etudid: {nom_RCS: RCS}}"""
def cree_rcss(self, etudiants: EtudiantsJuryPE, sxtags: dict[(str, int), pe_sxtag.SxTag]): self.rcfs: dict[tuple(int, str) : RCF] = {}
"""Créé tous les RCS, au regard du cursus des étudiants """Ensemble des RCF recensés : {(nom_RCS, fid_terminal): RCF}"""
analysés et des SxTag calculés.
Les mémorise dans les données de chaque étudiant. self.rcfs_suivis: dict[int:dict] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
son RCS : {etudid: {nom_RCS: RCF}}"""
self.rcrcfs: dict[tuple(int, str) : RCRCF] = {}
"""Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCRCF}"""
self.rcrcfs_suivis: dict[int:str] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
son RCRCF : {etudid: {nom_RCS: RCSx}}"""
def cree_rcss(self, etudiants: EtudiantsJuryPE):
"""Créé tous les RCS, au regard du cursus des étudiants
analysés + les mémorise dans les données de l'étudiant
Args: Args:
etudiants: Les étudiants à prendre en compte dans le Jury PE etudiants: Les étudiants à prendre en compte dans le Jury PE
pe_sxtag: Les Sx taggués
""" """
for nom_rcs in pe_comp.TOUS_LES_SEMESTRES + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM:
# 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)
noms_semestre_de_aggregat = TYPES_RCS[nom_rcs]["aggregat"] # ["S1", "S2", "S3"]
nom_semestre_terminal = noms_semestre_de_aggregat[-1] # "S3"
for etudid in etudiants.trajectoires: for etudid in etudiants.trajectoires:
if etudid not in self.suivi: self.rcss_suivis[etudid] = {
self.suivi[etudid] = {
aggregat: None aggregat: None
for aggregat in pe_comp.TOUS_LES_SEMESTRES for aggregat in pe_comp.TOUS_LES_SEMESTRES
+ TOUS_LES_RCS_AVEC_PLUSIEURS_SEM + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
} }
for nom_rcs in pe_comp.TOUS_LES_SEMESTRES + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM:
# 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)
noms_semestre_de_aggregat = TYPES_RCS[nom_rcs]["aggregat"]
nom_semestre_terminal = noms_semestre_de_aggregat[-1]
for etudid in etudiants.trajectoires:
# Le formsemestre terminal (dernier en date) associé au # Le formsemestre terminal (dernier en date) associé au
# semestre marquant la fin de l'aggrégat # semestre marquant la fin de l'aggrégat
# (par ex: son dernier S3 en date) # (par ex: son dernier S3 en date)
semestres = etudiants.trajectoires[etudid][nom_semestre_terminal] trajectoire = etudiants.trajectoires[etudid][nom_semestre_terminal]
if semestres: if trajectoire:
formsemestre_final = get_dernier_semestre_en_date(semestres) formsemestre_final = get_dernier_semestre_en_date(trajectoire)
# Ajout ou récupération de la trajectoire # Ajout ou 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.rcss: if rcs_id not in self.rcss:
rcs = RCS(nom_rcs, formsemestre_final) self.rcss[rcs_id] = RCF(nom_rcs, formsemestre_final)
self.rcss[rcs_id] = rcs
else:
rcs = self.rcss[rcs_id] rcs = self.rcss[rcs_id]
# 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_trajectoire_etudiant( semestres_a_aggreger = get_rcs_etudiant(
etudiants.trajectoires[etudid], formsemestre_final, nom_rcs etudiants.trajectoires[etudid], formsemestre_final, nom_rcs
) )
# Extrait les sxtags correspondants aux semestres à aggréger # Ajout des semestres au RCS
# (par ex. des 2 semestres S1(18)+S1(26) récupère le sxtag S1(26) rcs.add_semestres_a_aggreger(semestres_a_aggreger)
sxtags_a_aggreger = {}
# Mémorise le RCS suivi par l'étudiant
self.rcss_suivis[etudid][nom_rcs] = rcs
# Affichage pour debug
jeunes = list(enumerate(self.rcss_suivis))
for no_etud, etudid in jeunes[:20]:
pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :")
for nom_rcs, rcs in self.rcss_suivis[etudid].items():
if rcs:
pe_affichage.pe_print(f" > RCS {nom_rcs}: {rcs.get_repr()}")
def cree_rcfs(self, etudiants: EtudiantsJuryPE):
"""Créé les RCFs en ne conservant dans les RCS que les regroupements
de type Sx"""
self.rcfs = {}
for rcs_id, rcs in self.rcss.items():
if rcs and rcs.nom in pe_comp.TOUS_LES_SEMESTRES:
self.rcfs[rcs_id] = rcs
print(self.rcfs)
for etudid in self.rcss_suivis:
for nom_rcs, rcs in self.rcss_suivis[etudid].items():
if rcs and nom_rcs in pe_comp.TOUS_LES_SEMESTRES:
if etudid not in self.rcfs_suivis:
self.rcfs_suivis[etudid] = {}
self.rcfs_suivis[etudid][nom_rcs] = rcs
# Affichage pour debug
jeunes = list(enumerate(self.rcfs_suivis))
for no_etud, etudid in jeunes[:20]:
pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :")
for nom_rcs, rcs in self.rcfs_suivis[etudid].items():
if rcs:
pe_affichage.pe_print(f" > RCF {nom_rcs}: {rcs.get_repr()}")
else:
pe_affichage.pe_print(f" > RCF {nom_rcs}: <vide> !!! ")
def cree_rcrcfs(self, etudiants: EtudiantsJuryPE):
"""Créé tous les RCRCF, au regard du cursus des étudiants
analysés (trajectoires traduisant son parcours dans les
différents semestres) + les mémorise dans les données de l'étudiant
"""
# Pour tous les étudiants du jury
for etudid in self.rcss_suivis:
self.rcrcfs_suivis[etudid] = {}
for rcf_id, rcf in self.rcfs_suivis[etudid].items(): # Pour chaque RCS
semestres_a_aggreger = rcf.semestres_aggreges
# Tri des semestres par rang
semestres_tries = pe_comp.tri_semestres_par_rang(semestres_a_aggreger) semestres_tries = pe_comp.tri_semestres_par_rang(semestres_a_aggreger)
for rang in semestres_tries:
sems = semestres_tries[rang] # les 1 ou 2 semestres de même rang suivi
sxtag_id = pe_sxtag.get_sxtag_from_semestres(sems, sxtags)
if not sxtag_id:
raise ValueError(f"Il manque un sxtag pour {sems}")
sxtags_a_aggreger[sxtag_id] = sxtags[sxtag_id]
# Ajout des semestres à la trajectoire # Récupére les RCFs de type Sx traduisant sa trajectoire
rcs.add_sxtags_a_aggreger(sxtags_a_aggreger) rcfs_a_aggreger = {}
for semestres_du_rang in semestres_tries.values():
if semestres_du_rang:
rcf_id = get_rcf_from_semestres_aggreges(
self.rcfs, semestres_du_rang
)
if rcf_id:
raise ValueError(
"Il manque un RCF pour créer les RCRCFs dans cree_rcrcfs"
)
rcfs_a_aggreger[rcf_id] = self.rcfs[rcf_id]
# Mémoire la trajectoire suivie par l'étudiant # Ajout du RCRCF
self.suivi[etudid][nom_rcs] = rcs if rcf_id not in self.rcrcfs:
self.rcrfs[rcf_id] = RCRCF(rcf_id, rcf.formsemestre_final)
rcrcf = self.rcrcfs[rcf_id]
# Ajout des RCFs au RCRCF
rcrcf.add_rcfs_a_aggreger(rcfs_a_aggreger)
# Mémoire la trajectoire RCRCF suivie par l'étudiant
nom_rcs = rcrcf.nom
self.rcrcfs_suivis[etudid][nom_rcs] = rcrcf
def get_trajectoire_etudiant( def get_rcs_etudiant(
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
) -> dict[int, FormSemestre]: ) -> dict[int, FormSemestre]:
"""Ensemble des semestres parcourus (trajectoire) """Ensemble des semestres parcourus (trajectoire)
@ -273,9 +445,3 @@ def get_trajectoire_etudiant(
): ):
semestres_aggreges[fid] = semestre semestres_aggreges[fid] = semestre
return semestres_aggreges return semestres_aggreges
def get_descr_rcs(nom_rcs: str) -> str:
"""Renvoie la description pour les tableurs de synthèse
Excel d'un nom de RCS"""
return TYPES_RCS[nom_rcs]["descr"]

View File

@ -41,76 +41,81 @@ from app.pe import pe_affichage
from app.pe.pe_ressemtag import ResSemBUTTag from app.pe.pe_ressemtag import ResSemBUTTag
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from app.pe.pe_rcs import RCS from app.pe.pe_rcs import RCS, RCRCF
from app.pe.pe_sxtag import SxTag
from app.pe.pe_tabletags import TableTag from app.pe.pe_tabletags import TableTag
from app.pe.pe_moytag import MoyennesTag from app.pe.pe_moytag import MoyennesTag
class RCSTag(TableTag): class RCSTag(TableTag):
def __init__(self, rcrcf: RCS, sxstags: dict[(str, int): SxTag]):
def __init__( """Calcule les moyennes par tag (orientées compétences)
self, rcs: RCS, semestres_taggues: dict[int, ResSemBUTTag] d'un regroupement de SxTag
): (RCRCF), pour extraire les classements par tag pour un
"""Calcule les moyennes par tag d'une combinaison de semestres
(RCS), pour extraire les classements par tag pour un
groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous
participé au semestre terminal. participé au même semestre terminal.
Args: Args:
rcs: Un RCS (identifié par un nom et l'id de son semestre terminal) rcs: Un RCS (identifié par un nom et l'id de son semestre terminal)
semestres_taggues: Les données sur les semestres taggués sxstags: Les données sur les RCF taggués
""" """
TableTag.__init__(self) TableTag.__init__(self)
self.rcs_id: tuple(str, int) = rcs.rcs_id self.rcs_id: tuple(str, int) = rcrcf.rcs_id
"""Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)""" """Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)"""
self.rcs: RCS = rcs self.rcrcf: RCRCF = rcrcf
"""RCS associé au RCS taggué""" """RCRCF associé au RCS taggué"""
self.nom = self.get_repr() self.nom = self.get_repr()
"""Représentation textuelle du RCS taggué""" """Représentation textuelle du RCS taggué"""
self.formsemestre_terminal = rcs.formsemestre_final self.formsemestre_terminal = rcrcf.formsemestre_final
"""Le formsemestre terminal""" """Le formsemestre terminal"""
# Les résultats du formsemestre terminal # Les résultats du formsemestre terminal
nt = load_formsemestre_results(self.formsemestre_terminal) nt = load_formsemestre_results(self.formsemestre_terminal)
self.semestres_aggreges = rcs.semestres_aggreges self.rcfs_aggreges = rcrcf.rcfs_aggreges
"""Les semestres aggrégés""" """Les RCFs aggrégés"""
self.res_sems_tags = {} self.sxstags = {}
"""Les semestres tags associés aux semestres aggrégés""" """Les SxTag associés aux RCF aggrégés"""
try: try:
for frmsem_id in self.semestres_aggreges: for rcf_id in self.rcfs_aggreges:
self.res_sems_tags[frmsem_id] = semestres_taggues[frmsem_id] self.sxstags[rcf_id] = sxstags[rcf_id]
except: except:
raise ValueError("Semestres taggués manquants") raise ValueError("Semestres SxTag manquants")
# Les étudiants (etuds, états civils & etudis) # Les étudiants (etuds, états civils & etudis)
self.etuds = nt.etuds
self.add_etuds(nt.etuds) self.add_etuds(nt.etuds)
# Les compétences (extraites des ues de tous les semestres) # Les compétences (extraites de tous les Sxtags)
self.ues = self.comp_ues(tag="but") self.competences_sorted = self.do_complist()
"""Compétences extraites de tous les SxTag aggrégés"""
# Les tags # Les tags
self.tags_sorted = self.do_taglist() self.tags_sorted = self.do_taglist()
"""Tags extraits de tous les semestres""" """Tags extraits de tous les SxTag aggrégés"""
self.notes_cube = self.compute_notes_cube()
"""Cube de notes"""
etudids = list(self.etats_civils.keys())
self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted)
"""Calcul les moyennes par tag sous forme d'un dataframe"""
# Les moyennes
self.moyennes_tags: dict[str, MoyennesTag] = {} self.moyennes_tags: dict[str, MoyennesTag] = {}
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)""" """Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
for tag in self.tags_sorted: for tag in self.tags_sorted:
moy_gen_tag = self.notes[tag] # Cube de note
self.moyennes_tags[tag] = MoyennesTag(tag, moy_gen_tag) notes_cube, coeffs_cube = self.compute_notes_comps_cube(tag)
# Calcule des moyennes/coeffs sous forme d'un dataframe"""
moys_competences, coeffs_competences = compute_notes_competences(
notes_cube, coeffs_cube, self.etudids, self.competences_sorted
)
# Les moyennes
self.moyennes_tags[tag] = MoyennesTag(tag, moys_competences,
coeffs_competences)
def __eq__(self, other): def __eq__(self, other):
"""Egalité de 2 RCS taggués sur la base de leur identifiant""" """Egalité de 2 RCS taggués sur la base de leur identifiant"""
@ -119,11 +124,14 @@ class RCSTag(TableTag):
def get_repr(self, verbose=False) -> str: def get_repr(self, verbose=False) -> str:
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
est basée)""" est basée)"""
return self.rcs.get_repr(verbose=verbose) return self.rcrcf.get_repr(verbose=verbose)
def compute_notes_cube(self): def compute_notes_comps_cube(self, tag):
"""Construit le cube de notes (etudid x tags x semestre_aggregé) """Pour un tag donné, construit :
nécessaire au calcul des moyennes de l'aggrégat * le cube de notes (etudid x competences x SxTag) nécessaire au calcul des moyennes,
en remplaçant les données d'UE (obtenus du SxTag) par les compétences
* le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions)
appliqué au calcul des différents SxTag
""" """
# nb_tags = len(self.tags_sorted) # nb_tags = len(self.tags_sorted)
# nb_etudiants = len(self.etuds) # nb_etudiants = len(self.etuds)
@ -131,55 +139,90 @@ class RCSTag(TableTag):
# Index du cube (etudids -> dim 0, tags -> dim 1) # Index du cube (etudids -> dim 0, tags -> dim 1)
etudids = [etud.etudid for etud in self.etuds] etudids = [etud.etudid for etud in self.etuds]
tags = self.tags_sorted competences_sorted = self.competences_sorted
semestres_id = list(self.res_sems_tags.keys()) sxstags_ids = list(self.sxstags.keys())
dfs = {} notes_dfs = {}
coeffs_dfs = {}
for frmsem_id in semestres_id: for sxtag_id, sxtag in sxstags_ids.item():
# Partant d'un dataframe vierge # Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids, columns=tags) notes_df = pd.DataFrame(np.nan, index=etudids, columns=competences_sorted)
coeffs_df = pd.DataFrame(np.nan, index=etudids, columns=competences_sorted)
# Charge les notes du semestre tag moys_tag = sxtag.moyennes_tags[tag]
notes = self.res_sems_tags[frmsem_id].notes
# Les étudiants & les tags commun au dataframe final et aux notes du semestre) # Charge les notes et les coeffs du semestre tag
etudids_communs = df.index.intersection(notes.index) notes = moys_tag.matrice_notes.copy() # avec une copie
tags_communs = df.columns.intersection(notes.columns) coeffs = moys_tag.matrice_coeffs.copy() # les coeffs
# Injecte les notes par tag # Traduction des UE en compétences
df.loc[etudids_communs, tags_communs] = notes.loc[ association_ues_comp = moys_tag.competences
etudids_communs, tags_communs ues_columns_df = notes.columns
comp_associes_aux_ues = [association_ues_comp[ue] for ue in ues_columns_df]
notes.columns = comp_associes_aux_ues
coeffs.columns = comp_associes_aux_ues
# Compétences communes
comp_communes = list(set(competences_sorted) & set(comp_associes_aux_ues))
# Etudiants communs
etudids_communs = notes_df.index.intersection(notes.index)
# Recopie des notes et des coeffs
notes_df.loc[etudids_communs, comp_communes] = notes.loc[
etudids_communs, comp_communes
]
coeffs_df.loc[etudids_communs, comp_communes] = coeffs.loc[
etudids_communs, comp_communes
] ]
# Supprime tout ce qui n'est pas numérique # Supprime tout ce qui n'est pas numérique
for col in df.columns: for col in notes_df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce") notes_df[col] = pd.to_numeric(notes_df[col], errors="coerce")
# Stocke le df # Stocke les dfs
dfs[frmsem_id] = df notes_dfs[sxtag_id] = notes_df
coeffs_dfs[sxtag_id] = coeffs_df
"""Réunit les notes sous forme d'un cube etdids x tags x semestres""" """Réunit les notes sous forme d'un cube etudids x competences x semestres"""
semestres_x_etudids_x_tags = [dfs[fid].values for fid in dfs] sxtag_x_etudids_x_comps = [notes_dfs[fid].values for fid in notes_dfs]
etudids_x_tags_x_semestres = np.stack(semestres_x_etudids_x_tags, axis=-1) notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
return etudids_x_tags_x_semestres """Réunit les coeffs sous forme d'un cube etudids x competences x semestres"""
sxtag_x_etudids_x_comps = [coeffs_dfs[fid].values for fid in notes_dfs]
coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
return notes_etudids_x_comps_x_sxtag, coeffs_etudids_x_comps_x_sxtag
def do_taglist(self): def do_taglist(self):
"""Synthétise les tags à partir des semestres (taggués) aggrégés """Synthétise les tags à partir des Sxtags aggrégés
Returns: Returns:
Une liste de tags triés par ordre alphabétique Une liste de tags triés par ordre alphabétique
""" """
tags = [] tags = []
for frmsem_id in self.res_sems_tags: for frmsem_id in self.sxstags:
tags.extend(self.res_sems_tags[frmsem_id].tags_sorted) tags.extend(self.sxstags[frmsem_id].tags_sorted)
pe_affichage.pe_print(f"* Tags : {', '.join(tags)}") pe_affichage.pe_print(f"* Tags : {', '.join(tags)}")
return sorted(set(tags)) return sorted(set(tags))
def do_complist(self):
"""Synthétise les compétences à partir des Sxtags aggrégés"""
competences = []
for sxtag_id, sxtag in self.sxstags:
comp = sxtag.moyennes_tags["but"].competences
competences.extend(comp)
return sorted(set(competences))
def compute_notes_competences(
set_cube: np.array, coeff_cube: np.array, etudids: list, competences: list
):
"""Calcule:
* la moyenne par compétences à un tag donné sur plusieurs semestres (partant du set_cube).
* la somme des coeffs à utiliser pour la moyenne générale.
def compute_tag_moy(set_cube: np.array, etudids: list, tags: list):
"""Calcul de la moyenne par tag sur plusieurs semestres.
La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles
*Remarque* : Adaptation de moy_ue.compute_ue_moys_apc au cas des moyennes de tag *Remarque* : Adaptation de moy_ue.compute_ue_moys_apc au cas des moyennes de tag
@ -187,34 +230,41 @@ def compute_tag_moy(set_cube: np.array, etudids: list, tags: list):
Args: Args:
set_cube: notes moyennes aux modules ndarray set_cube: notes moyennes aux modules ndarray
(etuds x modimpls x UEs), des floats avec des NaN (etuds x UEs|compétences x sxtags), des floats avec des NaN
coeffs_cube: somme des coeffs impliqués dans la moyennes
etudids: liste des étudiants (dim. 0 du cube) etudids: liste des étudiants (dim. 0 du cube)
competences: list
tags: liste des tags (dim. 1 du cube) tags: liste des tags (dim. 1 du cube)
Returns: Returns:
Un DataFrame avec pour columns les moyennes par tags, Un DataFrame avec pour columns les moyennes par tags,
et pour rows les etudid et pour rows les etudid
""" """
nb_etuds, nb_tags, nb_semestres = set_cube.shape nb_etuds, nb_comps, nb_semestres = set_cube.shape
assert nb_etuds == len(etudids) assert nb_etuds == len(etudids)
assert nb_tags == len(tags) assert nb_comps == len(competences)
# Quelles entrées du cube contiennent des notes ? # Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube) mask = ~np.isnan(set_cube)
# Enlève les NaN du cube pour les entrées manquantes # Enlève les NaN du cube de notes pour les entrées manquantes
set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0) set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0)
coeffs_cube_no_nan = no.nan_to_num(coeff_cube, nan=0.0)
# Les moyennes par tag # Les moyennes par tag
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2) etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2)
# La somme des coeffs
coeff_tag = np.sum(coeffs_cube_no_nan, axis=2)
# Le dataFrame # Le dataFrame des notes moyennes
etud_moy_tag_df = pd.DataFrame( etud_moy_tag_df = pd.DataFrame(
etud_moy_tag, etud_moy_tag,
index=etudids, # les etudids index=etudids, # les etudids
columns=tags, # les tags columns=competences, # les competences
) )
etud_moy_tag_df.fillna(np.nan) etud_moy_tag_df.fillna(np.nan)
return etud_moy_tag_df coeffs_df = pd.DataFrame(coeff_tag, index=etudids, columns=competences)
coeffs_df.fillna(np.nan)
return etud_moy_tag_df, coeffs_df

View File

@ -85,11 +85,25 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
ues_hors_sport = [ue for ue in self.ues if ue.type != UE_SPORT] ues_hors_sport = [ue for ue in self.ues if ue.type != UE_SPORT]
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours() self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
# Les compétences associées aux UEs
self.competences = {}
for ue in self.ues:
if ue.type != UE_SPORT:
assert ue.niveau_competence, ScoValueError("Des UEs ne sont pas rattachées à des compétences")
nom = ue.niveau_competence.competence.titre
self.competences[ue.ue_id] = nom
# Les tags personnalisés et auto: # Les tags personnalisés et auto:
tags_dict = self._get_tags_dict() tags_dict = self._get_tags_dict()
self._check_tags(tags_dict) self._check_tags(tags_dict)
# self.tags = [tag for cat in dict_tags for tag in dict_tags[cat]] # self.tags = [tag for cat in dict_tags for tag in dict_tags[cat]]
# Les coefficients
matrice_coeffs = self.ues_inscr_parcours_df * [
ue.ects
for ue in ues_hors_sport # if ue.type != UE_SPORT <= déjà supprimé
]
# 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 = {}
@ -99,13 +113,10 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
moy_ues_tag = self.compute_moy_ues_tag(infos_tag) moy_ues_tag = self.compute_moy_ues_tag(infos_tag)
# moy_gen_tag = self.compute_moy_gen_tag(moy_ues_tag) # moy_gen_tag = self.compute_moy_gen_tag(moy_ues_tag)
ues_dict = {ue.id: ue for ue in ues_hors_sport}
self.moyennes_tags[tag] = MoyennesTag( self.moyennes_tags[tag] = MoyennesTag(
tag, tag,
ues_dict,
moy_ues_tag, moy_ues_tag,
self.ues_inscr_parcours_df matrice_coeffs
# moy_gen_tag
) )
# Ajoute les d'UE moyennes générales de BUT pour le semestre considéré # Ajoute les d'UE moyennes générales de BUT pour le semestre considéré
@ -117,12 +128,8 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
{ue.id: self.etud_moy_ue[ue.id] for ue in ues_hors_sport}, {ue.id: self.etud_moy_ue[ue.id] for ue in ues_hors_sport},
index=self.etudids, index=self.etudids,
) )
# moy_ues = self.nt.etud_moy_ue[ue_id]
# moy_gen_but = self.nt.etud_moy_gen
ues_dict = {ue.id: ue for ue in ues_hors_sport}
self.moyennes_tags["but"] = MoyennesTag( self.moyennes_tags["but"] = MoyennesTag(
"but", ues_dict, df_ues, self.ues_inscr_parcours_df # , moy_gen_but "but", df_ues, matrice_coeffs # , moy_gen_but
) )
self.tags_sorted = self.get_all_tags() self.tags_sorted = self.get_all_tags()
@ -183,6 +190,13 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
self.dispense_ues, self.dispense_ues,
block=self.formsemestre.block_moyennes, block=self.formsemestre.block_moyennes,
) )
# Transforme les UEs en acronyme
colonnes = moyennes_ues_tag.columns
ue_to_acro = {ue.id: ue.acronyme for ue in self.ues}
acronymes = [ue_to_acro[col] for col in colonnes]
moyennes_ues_tag.columns = acronymes
return moyennes_ues_tag return moyennes_ues_tag
def _get_tags_dict(self): def _get_tags_dict(self):
@ -299,27 +313,3 @@ def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
return synthese_tags return synthese_tags
def get_noms_competences_from_ues(formsemestre: FormSemestre) -> dict[int, str]:
"""Partant d'un formsemestre, extrait le nom des compétences associés
à (ou aux) parcours des étudiants du formsemestre.
Ignore les UEs non associées à un niveau de compétence.
Args:
formsemestre: Un FormSemestre
Returns:
Dictionnaire {ue_id: nom_competence} lisant tous les noms des compétences
en les raccrochant à leur ue
"""
# Les résultats du semestre
nt = load_formsemestre_results(formsemestre)
noms_competences = {}
for ue in nt.ues:
if ue.niveau_competence and ue.type != UE_SPORT:
# ?? inutilisé ordre = ue.niveau_competence.ordre
nom = ue.niveau_competence.competence.titre
noms_competences[ue.ue_id] = f"comp. {nom}"
return noms_competences

View File

@ -50,7 +50,7 @@ from app.pe.pe_moytag import MoyennesTag
class SxTag(TableTag): class SxTag(TableTag):
def __init__(self, sxtag_id: (int, int), ressembuttags: dict[int, ResSemBUTTag]): def __init__(self, sxtag_id: (int, int), ressembuttags: dict[int, ResSemBUTTag]):
"""Calcule les moyennes/classements par tag d'un semestre de type 'Sx' """Calcule les moyennes/classements par tag d'un semestre de type 'Sx'
(par ex. 'S1', 'S2', ...) : (par ex. 'S1', 'S2', ...) avec une orientation par UE :
* pour les étudiants non redoublants, ce sont les moyennes/classements * pour les étudiants non redoublants, ce sont les moyennes/classements
du semestre suivi du semestre suivi
@ -91,6 +91,7 @@ class SxTag(TableTag):
self.etuds = ressembuttags[self.fid_final].etuds self.etuds = ressembuttags[self.fid_final].etuds
self.add_etuds(self.etuds) self.add_etuds(self.etuds)
# Affichage
pe_affichage.pe_print(f"--> {self.get_repr()}") pe_affichage.pe_print(f"--> {self.get_repr()}")
# Les tags # Les tags
@ -99,38 +100,53 @@ class SxTag(TableTag):
pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}") pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}")
# Les UE # Les UE
self.ues = self.ressembuttag_final.moyennes_tags["but"].ues moy_sem_final = self.ressembuttag_final.moyennes_tags["but"]
self.ues = list(moy_sem_final.matrice_notes.columns)
# Les acronymes des UE # Les acronymes des UE
self.acronymes_ues_sorted = sorted([ue.acronyme for ue in self.ues.values()]) self.acronymes_ues_sorted = sorted(self.ues)
# Les inscriptions des étudiants aux UEs # Les inscriptions des étudiants aux UEs
# => ne conserve que les UEs du semestre final (pour les redoublants) # => ne conserve que les UEs du semestre final (pour les redoublants)
self.ues_inscr_parcours_df = self.ressembuttag_final.moyennes_tags[ self.matrice_coeffs = self.ressembuttag_final.moyennes_tags[
"but" "but"
].ues_inscr_parcours_df ].matrice_coeffs
self.ues_inscr_parcours = ~np.isnan(self.matrice_coeffs.to_numpy())
# Les moyennes par tag # Les moyennes par tag
self.moyennes_tags: dict[str, pd.DataFrame] = {} self.moyennes_tags: dict[str, pd.DataFrame] = {}
"""Les notes aux UEs dans différents tags""" """Les notes aux UEs dans différents tags"""
# Masque des inscriptions # Masque des inscriptions
inscr_mask = self.ues_inscr_parcours_df.to_numpy() inscr_mask = self.ues_inscr_parcours
for tag in self.tags_sorted: for tag in self.tags_sorted:
# Cube de note # Cube de note etudids x UEs
notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues_sorted) notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues_sorted)
# Calcule des moyennes sous forme d'un dataframe""" # Calcule des moyennes sous forme d'un dataframe"""
moys_ues = compute_notes_ues( matrice_moys_ues = compute_notes_ues(
notes_cube, notes_cube,
self.etudids, self.etudids,
self.acronymes_ues_sorted, self.acronymes_ues_sorted,
inscr_mask, inscr_mask,
) )
# Les profils d'ects (pour debug)
profils_ects = []
for i in self.matrice_coeffs.index:
val = tuple(self.matrice_coeffs.loc[i].fillna("x"))
if tuple(val) not in profils_ects:
profils_ects.append(tuple(val))
# Les moyennes # Les moyennes
self.moyennes_tags[tag] = MoyennesTag( self.moyennes_tags[tag] = MoyennesTag(tag,
tag, self.ues, moys_ues, self.ues_inscr_parcours_df matrice_moys_ues,
) self.matrice_coeffs)
pe_affichage.pe_print(f"> MoyTag pour {tag} avec")
pe_affichage.pe_print(f" - ues={self.acronymes_ues_sorted}")
pe_affichage.pe_print(f" - ects={profils_ects}")
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"""
@ -140,7 +156,7 @@ class SxTag(TableTag):
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
est basée)""" est basée)"""
affichage = [str(fid) for fid in self.ressembuttags] affichage = [str(fid) for fid in self.ressembuttags]
return f"S{self.sxtag_id[0]}Tag ({'+'.join(affichage)})" return f"{self.sxtag_id[0]}Tag ({'+'.join(affichage)})"
def compute_notes_ues_cube(self, tag, acronymes_ues_sorted): def compute_notes_ues_cube(self, tag, acronymes_ues_sorted):
"""Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé) """Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé)
@ -160,7 +176,7 @@ class SxTag(TableTag):
# Charge les notes du semestre tag # Charge les notes du semestre tag
sem_tag = self.ressembuttags[frmsem_id] sem_tag = self.ressembuttags[frmsem_id]
moys_tag = sem_tag.moyennes_tags[tag] moys_tag = sem_tag.moyennes_tags[tag]
notes = moys_tag.notes_ues # dataframe etudids x ues notes = moys_tag.matrice_notes # dataframe etudids x ues
acronymes_ues_sem = list( acronymes_ues_sem = list(
notes.columns notes.columns
) # les acronymes des UEs du semestre tag ) # les acronymes des UEs du semestre tag
@ -183,7 +199,7 @@ class SxTag(TableTag):
# Stocke le df # Stocke le df
dfs[frmsem_id] = df dfs[frmsem_id] = df
"""Réunit les notes sous forme d'un cube semestres x etdids x ues""" """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] 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 etudids_x_ues_x_semestres return etudids_x_ues_x_semestres

View File

@ -94,13 +94,13 @@ class TableTag(object):
tags_tries = self.get_all_tags() tags_tries = self.get_all_tags()
for tag in tags_tries: for tag in tags_tries:
moy_tag = self.moyennes_tags[tag] moy_tag = self.moyennes_tags[tag]
for acronyme in moy_tag.moys_ues: for acronyme in moy_tag.moyennes:
moy = moy_tag.moys_ues[acronyme] # une moyenne moy = moy_tag.moyennes[acronyme] # une moyenne
df = df.join(moy.synthese["notes"].rename(f"Moy {tag}-{acronyme}")) df = df.join(moy.synthese["notes"].rename(f"Moy {tag}-{acronyme}"))
df = df.join( df = df.join(
moy.synthese["classements"].rename(f"Class {tag}-{acronyme}") moy.synthese["classements"].rename(f"Class {tag}-{acronyme}")
) )
moy_gen = moy_tag.moy_gen moy_gen = moy_tag.moyenne_gen
df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag} (gen)")) df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag} (gen)"))
df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag} (gen)")) df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag} (gen)"))