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
7 changed files with 392 additions and 301 deletions
Showing only changes of commit 9e13b51669 - Show all commits

View File

@ -349,8 +349,8 @@ class EtudiantsJuryPE:
trajectoire = trajectoire_aggr[aggregat] trajectoire = trajectoire_aggr[aggregat]
if trajectoire: if trajectoire:
# Le semestre terminal de l'étudiant de l'aggrégat # Le semestre terminal de l'étudiant de l'aggrégat
fid = trajectoire.semestre_final.formsemestre_id fid = trajectoire.formsemestre_final.formsemestre_id
formsemestres_terminaux[fid] = trajectoire.semestre_final formsemestres_terminaux[fid] = trajectoire.formsemestre_final
return formsemestres_terminaux return formsemestres_terminaux
def get_formsemestres(self, semestres_recherches=None): def get_formsemestres(self, semestres_recherches=None):

View File

@ -1,5 +1,5 @@
from app.comp import moy_sem from app.comp import moy_sem
from app.pe.pe_tabletags import TableTag from app.pe.pe_tabletags import TableTag, MoyenneTag
from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE
from app.pe.pe_trajectoiretag import TrajectoireTag from app.pe.pe_trajectoiretag import TrajectoireTag
@ -10,7 +10,6 @@ import numpy as np
class AggregatInterclasseTag(TableTag): class AggregatInterclasseTag(TableTag):
# ------------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------------
def __init__( def __init__(
self, self,
@ -30,24 +29,30 @@ class AggregatInterclasseTag(TableTag):
""" """
TableTag.__init__(self) TableTag.__init__(self)
# Le nom
self.aggregat = nom_aggregat self.aggregat = nom_aggregat
"""Aggrégat de l'interclassement"""
self.nom = self.get_repr() self.nom = self.get_repr()
"""Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)""" # TODO """Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)""" # TODO
self.diplomes_ids = etudiants.etudiants_diplomes self.diplomes_ids = etudiants.etudiants_diplomes
self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids} self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids}
# pour les exports sous forme de dataFrame # pour les exports sous forme de dataFrame
self.etudiants = {etudid: etudiants.identites[etudid].etat_civil for etudid in self.diplomes_ids} self.etudiants = {
etudid: etudiants.identites[etudid].etat_civil
for etudid in self.diplomes_ids
}
# Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat # Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat
self.trajectoires: dict[int, Trajectoire] = {} self.trajectoires: dict[int, Trajectoire] = {}
"""Ensemble des trajectoires associées à l'aggrégat"""
for trajectoire_id in trajectoires_jury_pe.trajectoires: for trajectoire_id in trajectoires_jury_pe.trajectoires:
trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id] trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id]
if trajectoire_id[0] == nom_aggregat: if trajectoire_id[0] == nom_aggregat:
self.trajectoires[trajectoire_id] = trajectoire self.trajectoires[trajectoire_id] = trajectoire
self.trajectoires_taggues: dict[int, Trajectoire] = {} self.trajectoires_taggues: dict[int, Trajectoire] = {}
"""Ensemble des trajectoires tagguées associées à l'aggrégat"""
for trajectoire_id in self.trajectoires: for trajectoire_id in self.trajectoires:
self.trajectoires_taggues[trajectoire_id] = trajectoires_taggues[ self.trajectoires_taggues[trajectoire_id] = trajectoires_taggues[
trajectoire_id trajectoire_id
@ -56,26 +61,27 @@ class AggregatInterclasseTag(TableTag):
# Les trajectoires suivies par les étudiants du jury, en ne gardant que # Les trajectoires suivies par les étudiants du jury, en ne gardant que
# celles associées aux diplomés # celles associées aux diplomés
self.suivi: dict[int, Trajectoire] = {} self.suivi: dict[int, Trajectoire] = {}
"""Association entre chaque étudiant et la trajectoire tagguée à prendre en
compte pour l'aggrégat"""
for etudid in self.diplomes_ids: for etudid in self.diplomes_ids:
self.suivi[etudid] = trajectoires_jury_pe.suivi[etudid][nom_aggregat] self.suivi[etudid] = trajectoires_jury_pe.suivi[etudid][nom_aggregat]
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)"""
# Construit la matrice de notes # Construit la matrice de notes
self.notes = self.compute_notes_matrice() self.notes = self.compute_notes_matrice()
"""Matrice des notes de l'aggrégat"""
# Synthétise les moyennes/classements par tag # Synthétise les moyennes/classements par tag
self.moyennes_tags = {} self.moyennes_tags: dict[str, MoyenneTag] = {}
for tag in self.tags_sorted: for tag in self.tags_sorted:
moy_gen_tag = self.notes[tag] moy_gen_tag = self.notes[tag]
self.moyennes_tags[tag] = self.comp_moy_et_stat(moy_gen_tag) self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)
# Est significatif ? (aka a-t-il des tags et des notes) # Est significatif ? (aka a-t-il des tags et des notes)
self.significatif = len(self.tags_sorted) > 0 self.significatif = len(self.tags_sorted) > 0
def get_repr(self) -> str: def get_repr(self) -> str:
"""Une représentation textuelle""" """Une représentation textuelle"""
return f"Aggrégat {self.aggregat}" return f"Aggrégat {self.aggregat}"
@ -118,7 +124,4 @@ class AggregatInterclasseTag(TableTag):
etudids_communs, tags_communs etudids_communs, tags_communs
] ]
# Force les nan
df.fillna(np.nan)
return df return df

View File

@ -65,35 +65,15 @@ import pandas as pd
class JuryPE(object): class JuryPE(object):
"""Classe mémorisant toutes les informations nécessaires pour établir un jury de PE.
Modèle basé sur NotesTable.
Attributs :
* diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX)
* juryEtudDict : dictionnaire récapitulant les étudiants participant au jury PE (données administratives +
celles des semestres valides à prendre en compte permettant le calcul des moyennes ...
``{'etudid : { 'nom', 'prenom', 'civilite', 'diplome', '', }}``
a
Rq: il contient à la fois les étudiants qui vont être diplomés à la date prévue
et ceux qui sont éliminés (abandon, redoublement, ...) pour affichage alternatif
"""
# Variables de classe décrivant les aggrégats, leur ordre d'apparition temporelle et
# leur affichage dans les avis latex
# ------------------------------------------------------------------------------------------------------------------
def __init__(self, diplome): def __init__(self, diplome):
""" """
Création d'une table PE sur la base d'un semestre selectionné. De ce semestre est déduit : Classe mémorisant toutes les informations nécessaires pour établir un jury de PE, sur la base
d'une année de diplôme. De ce semestre est déduit :
1. l'année d'obtention du DUT, 1. l'année d'obtention du DUT,
2. tous les étudiants susceptibles à ce stade (au regard de leur parcours) d'être diplomés. 2. tous les étudiants susceptibles à ce stade (au regard de leur parcours) d'être diplomés.
Args: Args:
sem_base: le FormSemestre donnant le semestre à la base du jury PE diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX)
semBase: le dictionnaire sem donnant la base du jury (CB: TODO: A supprimer à long term)
meme_programme: si True, impose un même programme pour tous les étudiants participant au jury,
si False, permet des programmes differents
""" """
self.diplome = diplome self.diplome = diplome
"L'année du diplome" "L'année du diplome"
@ -101,7 +81,7 @@ class JuryPE(object):
self.nom_export_zip = f"Jury_PE_{self.diplome}" self.nom_export_zip = f"Jury_PE_{self.diplome}"
"Nom du zip où ranger les fichiers générés" "Nom du zip où ranger les fichiers générés"
# Chargement des étudiants à prendre en compte dans le jury # Chargement des étudiants à prendre en compte Sydans le jury
pe_affichage.pe_print( pe_affichage.pe_print(
f"""*** Recherche et chargement des étudiants diplômés en { f"""*** Recherche et chargement des étudiants diplômés en {
self.diplome}""" self.diplome}"""
@ -122,7 +102,6 @@ class JuryPE(object):
self._gen_xls_synthese_jury_par_tag(zipfile) self._gen_xls_synthese_jury_par_tag(zipfile)
self._gen_xls_synthese_par_etudiant(zipfile) self._gen_xls_synthese_par_etudiant(zipfile)
# Fin !!!! Tada :) # Fin !!!! Tada :)
def _gen_xls_diplomes(self, zipfile: ZipFile): def _gen_xls_diplomes(self, zipfile: ZipFile):
@ -342,48 +321,131 @@ class JuryPE(object):
""" """
etudids = list(self.diplomes_ids) etudids = list(self.diplomes_ids)
aggregats = pe_comp.TOUS_LES_PARCOURS
donnees = {}
# Les données des étudiants
donnees_etudiants = {}
for etudid in etudids: for etudid in etudids:
etudiant = self.etudiants.identites[etudid] etudiant = self.etudiants.identites[etudid]
donnees[etudid] = { donnees_etudiants[etudid] = {
("Identité", "", "Civilite"): etudiant.civilite_str, ("Identité", "", "Civilite"): etudiant.civilite_str,
("Identité", "", "Nom"): etudiant.nom, ("Identité", "", "Nom"): etudiant.nom,
("Identité", "", "Prenom"): etudiant.prenom, ("Identité", "", "Prenom"): etudiant.prenom,
} }
df_synthese = pd.DataFrame.from_dict(donnees_etudiants, orient="index")
for aggregat in aggregats: # Ajout des aggrégats
# Le dictionnaire par défaut des moyennes aggregats = pe_comp.TOUS_LES_PARCOURS
donnees[etudid] |= get_defaut_dict_synthese_aggregat(aggregat, self.diplome)
# La trajectoire de l'étudiant sur l'aggrégat for aggregat in aggregats:
descr = pe_comp.PARCOURS[aggregat]["descr"]
# Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag
# considéré
trajectoires_tagguees = []
for etudid in etudids:
trajectoire = self.trajectoires.suivi[etudid][aggregat] trajectoire = self.trajectoires.suivi[etudid][aggregat]
if trajectoire: if trajectoire:
trajectoire_tagguee = self.trajectoires_tagguees[ tid = trajectoire.trajectoire_id
trajectoire.trajectoire_id trajectoire_tagguee = self.trajectoires_tagguees[tid]
] if (
else: tag in trajectoire_tagguee.moyennes_tags
trajectoire_tagguee = None and trajectoire_tagguee not in trajectoires_tagguees
):
trajectoires_tagguees.append(trajectoire_tagguee)
# L'interclassement # Ajout des notes
notes = pd.DataFrame(index=etudids, columns=[ [descr], [""], ["note"] ])
nbre_notes_injectees = 0
for traj in trajectoires_tagguees:
moy_traj = traj.moyennes_tags[tag]
notes_traj = moy_traj.get_df_notes(arrondi=True)
etudids_communs = notes_traj.index.intersection(etudids)
nbre_notes_injectees += len(etudids_communs)
notes.loc[etudids_communs, (descr, "", "note")] = notes_traj.loc[etudids_communs, "notes"]
# Si l'aggrégat est significatif (aka il y a des notes)
if nbre_notes_injectees > 0:
df_synthese = df_synthese.join(notes)
# Ajout des classements & statistiques
donnees = pd.DataFrame(
index=etudids,
columns=[ [descr]*4, [NOM_STAT_GROUPE]*4, ["class.", "min", "moy", "max"] ],
)
# donnees[(descr, NOM_STAT_GROUPE, "class.")] = donnees[
# (descr, NOM_STAT_GROUPE, "class.")
# ].astype(str)
# donnees[(descr, NOM_STAT_GROUPE, "class.")] = np.nan
for traj in trajectoires_tagguees:
moy_traj = traj.moyennes_tags[tag]
# Les classements
rangs = moy_traj.get_df_rangs_pertinents()
# Les etudids communs pour la trajectoire
etudids_communs = rangs.index.intersection(etudids)
donnees.loc[
etudids_communs, (descr, NOM_STAT_GROUPE, "class.")
] = rangs.loc[etudids_communs, "rangs"]
# Le min
donnees.loc[
etudids_communs, (descr, NOM_STAT_GROUPE, "min")
] = moy_traj.get_min_for_df()
# Le max
donnees.loc[
etudids_communs, (descr, NOM_STAT_GROUPE, "max")
] = moy_traj.get_max_for_df()
# La moyenne
donnees.loc[
etudids_communs, (descr, NOM_STAT_GROUPE, "moy")
] = moy_traj.get_moy_for_df()
df_synthese = df_synthese.join(donnees)
# Ajoute les données d'interclassement
interclass = self.interclassements_taggues[aggregat] interclass = self.interclassements_taggues[aggregat]
moy_traj = interclass.moyennes_tags[tag]
# Injection des données dans un dictionnaire nom_stat_promo = f"{NOM_STAT_PROMO} {self.diplome}"
donnees[etudid] |= get_dict_synthese_aggregat(aggregat, trajectoire_tagguee, interclass, etudid, tag, self.diplome) donnees = pd.DataFrame(
index=etudids,
columns=[ [descr]*4, [nom_stat_promo]*4, ["class.", "min", "moy", "max"] ],
)
# Les classements
rangs = moy_traj.get_df_rangs_pertinents()
etudids_communs = rangs.index.intersection(etudids)
donnees.loc[
etudids_communs, (descr, nom_stat_promo, "class.")
] = rangs.loc[etudids_communs, "rangs"]
# Le min
donnees.loc[
etudids_communs, (descr, nom_stat_promo, "min")
] = moy_traj.get_min_for_df()
# Le max
donnees.loc[
etudids_communs, (descr, nom_stat_promo, "max")
] = moy_traj.get_max_for_df()
# La moyenne
donnees.loc[
etudids_communs, (descr, nom_stat_promo, "moy")
] = moy_traj.get_moy_for_df()
df_synthese = df_synthese.join(donnees)
# Fin de l'aggrégat # Fin de l'aggrégat
# Construction du dataFrame
df = pd.DataFrame.from_dict(donnees, orient="index")
# Tri par nom/prénom # Tri par nom/prénom
df.sort_values( df_synthese.sort_values(
by=[("Identité", "", "Nom"), ("Identité", "", "Prenom")], inplace=True by=[("Identité", "", "Nom"), ("Identité", "", "Prenom")], inplace=True
) )
return df return df_synthese
def synthetise_jury_par_etudiants(self) -> dict[pd.DataFrame]: def synthetise_jury_par_etudiants(self) -> dict[pd.DataFrame]:
"""Synthétise tous les résultats du jury PE dans des dataframes, """Synthétise tous les résultats du jury PE dans des dataframes,
@ -424,7 +486,9 @@ class JuryPE(object):
for aggregat in aggregats: for aggregat in aggregats:
# Le dictionnaire par défaut des moyennes # Le dictionnaire par défaut des moyennes
donnees[tag] |= get_defaut_dict_synthese_aggregat(aggregat, self.diplome) donnees[tag] |= get_defaut_dict_synthese_aggregat(
aggregat, self.diplome
)
# La trajectoire de l'étudiant sur l'aggrégat # La trajectoire de l'étudiant sur l'aggrégat
trajectoire = self.trajectoires.suivi[etudid][aggregat] trajectoire = self.trajectoires.suivi[etudid][aggregat]
@ -432,26 +496,29 @@ class JuryPE(object):
trajectoire_tagguee = self.trajectoires_tagguees[ trajectoire_tagguee = self.trajectoires_tagguees[
trajectoire.trajectoire_id trajectoire.trajectoire_id
] ]
else: if tag in trajectoire_tagguee.moyennes_tags:
trajectoire_tagguee = None # L'interclassement
interclass = self.interclassements_taggues[aggregat]
# L'interclassement
interclass = self.interclassements_taggues[aggregat]
# Injection des données dans un dictionnaire
donnees[tag] |= get_dict_synthese_aggregat(aggregat, trajectoire_tagguee, interclass, etudid, tag, self.diplome)
# Injection des données dans un dictionnaire
donnees[tag] |= get_dict_synthese_aggregat(
aggregat,
trajectoire_tagguee,
interclass,
etudid,
tag,
self.diplome,
)
# Fin de l'aggrégat # Fin de l'aggrégat
# Construction du dataFrame # Construction du dataFrame
df = pd.DataFrame.from_dict(donnees, orient="index") df = pd.DataFrame.from_dict(donnees, orient="index")
# Tri par nom/prénom # Tri par nom/prénom
df.sort_values( df.sort_values(by=[("", "", "tag")], inplace=True)
by=[("", "", "tag")], inplace=True
)
return df return df
def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict: def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
"""Créé les semestres taggués, de type 'S1', 'S2', ..., pour un groupe d'étudiants donnés. """Créé les semestres taggués, de type 'S1', 'S2', ..., pour un groupe d'étudiants donnés.
Chaque semestre taggué est rattaché à l'un des FormSemestre faisant partie du cursus scolaire Chaque semestre taggué est rattaché à l'un des FormSemestre faisant partie du cursus scolaire
@ -587,7 +654,7 @@ def get_dict_synthese_aggregat(
interclassement_taggue: AggregatInterclasseTag, interclassement_taggue: AggregatInterclasseTag,
etudid: int, etudid: int,
tag: str, tag: str,
diplome: int diplome: int,
): ):
"""Renvoie le dictionnaire (à intégrer au tableur excel de synthese) """Renvoie le dictionnaire (à intégrer au tableur excel de synthese)
traduisant les résultats (moy/class) d'un étudiant à une trajectoire tagguée associée traduisant les résultats (moy/class) d'un étudiant à une trajectoire tagguée associée
@ -600,66 +667,43 @@ def get_dict_synthese_aggregat(
note = np.nan note = np.nan
# Les données de la trajectoire tagguée pour le tag considéré # Les données de la trajectoire tagguée pour le tag considéré
if trajectoire_tagguee and tag in trajectoire_tagguee.moyennes_tags: moy_tag = trajectoire_tagguee.moyennes_tags[tag]
bilan = trajectoire_tagguee.moyennes_tags[tag]
# La moyenne de l'étudiant # Les données de l'étudiant
note = TableTag.get_note_for_df(bilan, etudid) note = moy_tag.get_note_for_df(etudid)
# Statistiques sur le groupe classement = moy_tag.get_class_for_df(etudid)
if not pd.isna(note) and note != np.nan: nmin = moy_tag.get_min_for_df()
# Les moyennes de cette trajectoire nmax = moy_tag.get_max_for_df()
donnees |= { nmoy = moy_tag.get_moy_for_df()
(descr, "", "note"): note,
( # Statistiques sur le groupe
descr, if not pd.isna(note) and note != np.nan:
NOM_STAT_GROUPE, # Les moyennes de cette trajectoire
"class.", donnees |= {
): TableTag.get_class_for_df(bilan, etudid), (descr, "", "note"): note,
( (descr, NOM_STAT_GROUPE, "class."): classement,
descr, (descr, NOM_STAT_GROUPE, "min"): nmin,
NOM_STAT_GROUPE, (descr, NOM_STAT_GROUPE, "moy"): nmoy,
"min", (descr, NOM_STAT_GROUPE, "max"): nmax,
): TableTag.get_min_for_df(bilan), }
(
descr,
NOM_STAT_GROUPE,
"moy",
): TableTag.get_moy_for_df(bilan),
(
descr,
NOM_STAT_GROUPE,
"max",
): TableTag.get_max_for_df(bilan),
}
# L'interclassement # L'interclassement
if tag in interclassement_taggue.moyennes_tags: moy_tag = interclassement_taggue.moyennes_tags[tag]
bilan = interclassement_taggue.moyennes_tags[tag]
if not pd.isna(note) and note != np.nan: classement = moy_tag.get_class_for_df(etudid)
nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}" nmin = moy_tag.get_min_for_df()
nmax = moy_tag.get_max_for_df()
nmoy = moy_tag.get_moy_for_df()
if not pd.isna(note) and note != np.nan:
nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}"
donnees |= {
(descr, nom_stat_promo, "class."): classement,
(descr, nom_stat_promo, "min"): nmin,
(descr, nom_stat_promo, "moy"): nmoy,
(descr, nom_stat_promo, "max"): nmax,
}
donnees |= {
(
descr,
nom_stat_promo,
"class.",
): TableTag.get_class_for_df(bilan, etudid),
(
descr,
nom_stat_promo,
"min",
): TableTag.get_min_for_df(bilan),
(
descr,
nom_stat_promo,
"moy",
): TableTag.get_moy_for_df(bilan),
(
descr,
nom_stat_promo,
"max",
): TableTag.get_max_for_df(bilan),
}
return donnees return donnees

View File

@ -38,7 +38,7 @@ Created on Fri Sep 9 09:15:05 2016
import numpy as np import numpy as np
import app.pe.pe_etudiant import app.pe.pe_etudiant
from app import db, log from app import db, log, ScoValueError
from app.comp import res_sem, moy_ue, moy_sem from app.comp import res_sem, moy_ue, moy_sem
from app.comp.moy_sem import comp_ranks_series from app.comp.moy_sem import comp_ranks_series
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
@ -49,7 +49,7 @@ from app.models.moduleimpls import ModuleImpl
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.scodoc.codes_cursus import UE_SPORT from app.scodoc.codes_cursus import UE_SPORT
import app.pe.pe_affichage as pe_affichage import app.pe.pe_affichage as pe_affichage
from app.pe.pe_tabletags import TableTag, TAGS_RESERVES from app.pe.pe_tabletags import TableTag, TAGS_RESERVES, MoyenneTag
import pandas as pd import pandas as pd
@ -94,44 +94,51 @@ class SemestreTag(TableTag):
# Les tags : # Les tags :
## Saisis par l'utilisateur ## Saisis par l'utilisateur
self.tags_personnalises = get_synthese_tags_personnalises_semestre( tags_personnalises = get_synthese_tags_personnalises_semestre(
self.nt.formsemestre self.nt.formsemestre
) )
## Déduit des compétences ## Déduit des compétences
self.tags_competences = get_noms_competences_from_ues(self.nt.formsemestre) dict_ues_competences = get_noms_competences_from_ues(self.nt.formsemestre)
# Supprime les doublons dans les tags self.tags = (
tags_reserves = TAGS_RESERVES + list(self.tags_competences.values()) list(tags_personnalises.keys())
for tag in self.tags_personnalises: + list(dict_ues_competences.values())
if tag in tags_reserves: + ["but"]
del self.tags_personnalises[tag] )
pe_affichage.pe_print(f"Supprime le tag {tag}") """Tags du semestre taggué"""
## Vérifie l'unicité des tags
if len(set(self.tags)) != len(self.tags):
raise ScoValueError(
f"""Erreur dans le module PE : L'un des tags saisis dans le programme
fait parti des tags réservés (par ex. "comp. <titre_compétence>"). Modifiez les
tags de votre programme"""
)
# 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 = {}
for tag in self.tags_personnalises: for tag in tags_personnalises:
# pe_affichage.pe_print(f" -> Traitement du tag {tag}") # pe_affichage.pe_print(f" -> Traitement du tag {tag}")
moy_gen_tag = self.compute_moyenne_tag(tag) moy_gen_tag = self.compute_moyenne_tag(tag, tags_personnalises)
self.moyennes_tags[tag] = self.comp_moy_et_stat(moy_gen_tag) self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)
# Ajoute les moyennes générales de BUT pour le semestre considéré # Ajoute les moyennes générales de BUT pour le semestre considéré
moy_gen_but = self.nt.etud_moy_gen moy_gen_but = self.nt.etud_moy_gen
moy_gen_but = pd.to_numeric(moy_gen_but, errors="coerce") self.moyennes_tags["but"] = MoyenneTag("but", moy_gen_but)
self.moyennes_tags["but"] = self.comp_moy_et_stat(moy_gen_but)
# Ajoute les moyennes par compétence # Ajoute les moyennes par compétence
for ue_id, competence in self.tags_competences.items(): for ue_id, competence in dict_ues_competences.items():
moy_ue = self.nt.etud_moy_ue[ue_id] moy_ue = self.nt.etud_moy_ue[ue_id]
self.moyennes_tags[competence] = self.comp_moy_et_stat(moy_ue) self.moyennes_tags[competence] = MoyenneTag(competence, moy_ue)
self.tags_sorted = self.get_all_tags()
"""Tags (personnalisés+compétences) par ordre alphabétique"""
# Synthétise l'ensemble des moyennes dans un dataframe # Synthétise l'ensemble des moyennes dans un dataframe
self.tags_sorted = sorted(
self.moyennes_tags self.notes = self.df_notes()
) # les tags (personnalisés+compétences) par ordre alphabétique """Dataframe synthétique des notes par tag"""
self.notes = (
self.df_notes()
) # Le dataframe synthétique des notes (=moyennes par tag)
pe_affichage.pe_print( pe_affichage.pe_print(
f" => Traitement des tags {', '.join(self.tags_sorted)}" f" => Traitement des tags {', '.join(self.tags_sorted)}"
@ -141,9 +148,10 @@ class SemestreTag(TableTag):
"""Nom affiché pour le semestre taggué""" """Nom affiché pour le semestre taggué"""
return app.pe.pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True) return app.pe.pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
def compute_moyenne_tag(self, tag: str) -> list: def compute_moyenne_tag(self, tag: str, tags_infos: dict) -> pd.Series:
"""Calcule la moyenne des étudiants pour le tag indiqué, """Calcule la moyenne des étudiants pour le tag indiqué,
pour ce SemestreTag. pour ce SemestreTag, en ayant connaissance des informations sur
les tags (dictionnaire donnant les coeff de repondération)
Sont pris en compte les modules implémentés associés au tag, Sont pris en compte les modules implémentés associés au tag,
avec leur éventuel coefficient de **repondération**, en utilisant les notes avec leur éventuel coefficient de **repondération**, en utilisant les notes
@ -151,8 +159,8 @@ class SemestreTag(TableTag):
Force ou non le calcul de la moyenne lorsque des notes sont manquantes. Force ou non le calcul de la moyenne lorsque des notes sont manquantes.
Renvoie les informations sous la forme d'une liste Returns:
[ (moy, somme_coeff_normalise, etudid), ...] La série des moyennes
""" """
"""Adaptation du mask de calcul des moyennes au tag visé""" """Adaptation du mask de calcul des moyennes au tag visé"""
@ -163,13 +171,13 @@ class SemestreTag(TableTag):
"""Désactive tous les modules qui ne sont pas pris en compte pour ce tag""" """Désactive tous les modules qui ne sont pas pris en compte pour ce tag"""
for i, modimpl in enumerate(self.formsemestre.modimpls_sorted): for i, modimpl in enumerate(self.formsemestre.modimpls_sorted):
if modimpl.moduleimpl_id not in self.tags_personnalises[tag]: if modimpl.moduleimpl_id not in tags_infos[tag]:
modimpls_mask[i] = False 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 self.tags_personnalises[tag]: for modimpl_id in tags_infos[tag]:
ponderation = self.tags_personnalises[tag][modimpl_id]["ponderation"] ponderation = tags_infos[tag][modimpl_id]["ponderation"]
modimpl_coefs_ponderes_df[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)"""

View File

@ -40,8 +40,10 @@ Created on Thu Sep 8 09:36:33 2016
import datetime import datetime
import numpy as np import numpy as np
from app import ScoValueError
from app.comp.moy_sem import comp_ranks_series from app.comp.moy_sem import comp_ranks_series
from app.pe import pe_affichage from app.pe import pe_affichage
from app.pe.pe_affichage import SANS_NOTE
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
import pandas as pd import pandas as pd
@ -49,62 +51,34 @@ import pandas as pd
TAGS_RESERVES = ["but"] TAGS_RESERVES = ["but"]
class TableTag(object): class MoyenneTag:
def __init__(self): def __init__(self, tag: str, notes: pd.Series):
"""Classe centralisant différentes méthodes communes aux """Classe centralisant la synthèse des moyennes/classements d'une série
SemestreTag, TrajectoireTag, AggregatInterclassTag d'étudiants à un tag donné, en stockant un dictionnaire :
``
{
"notes": la Serie pandas des notes (float),
"classements": la Serie pandas des classements (float),
"min": la note minimum,
"max": la note maximum,
"moy": la moyenne,
"nb_inscrits": le nombre d'étudiants ayant une note,
}
``
Args:
tag: Un tag
note: Une série de notes (moyenne) sous forme d'un pd.Series()
""" """
pass self.tag = tag
"""Le tag associé à la moyenne"""
self.synthese = self.comp_moy_et_stat(notes)
"""La synthèse des notes/classements/statistiques"""
# ----------------------------------------------------------------------------------------------------------- def __eq__(self, other):
def get_all_tags(self): """Egalité de deux MoyenneTag lorsque leur tag sont identiques"""
"""Liste des tags de la table, triée par ordre alphabétique return self.tag == other.tag
Returns:
Liste de tags triés par ordre alphabétique
"""
return sorted(self.moyennes_tags.keys())
def df_moyennes_et_classements(self):
"""Renvoie un dataframe listant toutes les moyennes,
et les classements des étudiants pour tous les tags
"""
etudiants = self.etudiants
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
for tag in self.get_all_tags():
df = df.join(self.moyennes_tags[tag]["notes"].rename(f"Moy {tag}"))
df = df.join(self.moyennes_tags[tag]["classements"].rename(f"Class {tag}"))
return df
def df_notes(self):
"""Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags
Returns:
Un dataframe etudids x tag (avec tag par ordre alphabétique)
"""
tags = self.get_all_tags()
if tags:
dict_series = {tag: self.moyennes_tags[tag]["notes"] for tag in tags}
df = pd.DataFrame(dict_series)
return df
else:
return None
def str_tagtable(self):
"""Renvoie une chaine de caractère listant toutes les moyennes,
les rangs des étudiants pour tous les tags."""
etudiants = self.etudiants
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
for tag in self.get_all_tags():
df = df.join(self.moyennes_tags[tag]["notes"].rename(f"moy {tag}"))
df = df.join(self.moyennes_tags[tag]["classements"].rename(f"class {tag}"))
return df.to_csv(sep=";")
def comp_moy_et_stat(self, notes: pd.Series) -> dict: def comp_moy_et_stat(self, notes: pd.Series) -> dict:
"""Calcule et structure les données nécessaires au PE pour une série """Calcule et structure les données nécessaires au PE pour une série
@ -131,7 +105,7 @@ class TableTag(object):
(_, class_gen_ue_non_nul) = comp_ranks_series(notes_non_nulles) (_, class_gen_ue_non_nul) = comp_ranks_series(notes_non_nulles)
# Les classements (toutes notes confondues, avec NaN si pas de notes) # Les classements (toutes notes confondues, avec NaN si pas de notes)
class_gen_ue = pd.Series(np.nan, index=notes.index, dtype="Int64") class_gen_ue = pd.Series(np.nan, index=notes.index) # , dtype="Int64")
class_gen_ue[indices] = class_gen_ue_non_nul[indices] class_gen_ue[indices] = class_gen_ue_non_nul[indices]
synthese = { synthese = {
@ -140,42 +114,115 @@ class TableTag(object):
"min": notes.min(), "min": notes.min(),
"max": notes.max(), "max": notes.max(),
"moy": notes.mean(), "moy": notes.mean(),
"nb_inscrits": len(indices), "nb_inscrits": sum(indices),
} }
return synthese return synthese
@classmethod def get_df_notes(self, arrondi=False):
def get_min_for_df(cls, bilan: dict) -> float: """Série des notes, arrondies à 2 chiffres après la virgule"""
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné, if arrondi:
revoie le min renseigné pour affichage dans un df""" serie = self.synthese["notes"].round(2)
return round(bilan["min"], 2) else:
serie = self.synthese["notes"]
df = serie.to_frame("notes")
return df
@classmethod def get_df_rangs_pertinents(self) -> pd.Series:
def get_max_for_df(cls, bilan: dict) -> float: """Série des rangs classement/nbre_inscrit"""
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné, classement = self.synthese["classements"]
renvoie le max renseigné pour affichage dans un df""" indices = classement[classement.notnull()].index.to_list()
return round(bilan["max"], 2) classement_non_nul = classement.loc[indices].to_frame("classements")
classement_non_nul.insert(1, "rangs", np.nan)
@classmethod nb_inscrit = self.synthese["nb_inscrits"]
def get_moy_for_df(cls, bilan: dict) -> float:
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné,
renvoie la moyenne renseignée pour affichage dans un df"""
return round(bilan["moy"], 2)
@classmethod classement_non_nul["rangs"] = classement_non_nul["classements"].astype(int).astype(str) + "/" + str(nb_inscrit)
def get_class_for_df(cls, bilan: dict, etudid: int) -> str: return classement_non_nul["rangs"].to_frame("rangs")
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné,
renvoie le classement ramené au nombre d'inscrits, def get_note_for_df(self, etudid: int):
"""Note d'un étudiant donné par son etudid"""
return round(self.synthese["notes"].loc[etudid], 2)
def get_min_for_df(self) -> float:
"""Min renseigné pour affichage dans un df"""
return round(self.synthese["min"], 2)
def get_max_for_df(self) -> float:
"""Max renseigné pour affichage dans un df"""
return round(self.synthese["max"], 2)
def get_moy_for_df(self) -> float:
"""Moyenne renseignée pour affichage dans un df"""
return round(self.synthese["moy"], 2)
def get_class_for_df(self, etudid: int) -> str:
"""Classement ramené au nombre d'inscrits,
pour un étudiant donné par son etudid""" pour un étudiant donné par son etudid"""
classement = bilan['classements'].loc[etudid] classement = self.synthese["classements"].loc[etudid]
nb_inscrit = self.synthese["nb_inscrits"]
if not pd.isna(classement): if not pd.isna(classement):
return f"{classement}/{bilan['nb_inscrits']}" classement = int(classement)
return f"{classement}/{nb_inscrit}"
else: else:
return pe_affichage.SANS_NOTE return pe_affichage.SANS_NOTE
@classmethod def is_significatif(self) -> bool:
def get_note_for_df(cls, bilan: dict, etudid: int): """Indique si la moyenne est significative (c'est-à-dire à des notes)"""
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné, return self.synthese["nb_inscrits"] > 0
renvoie la note (moyenne)
pour un étudiant donné par son etudid"""
return round(bilan["notes"].loc[etudid], 2) class TableTag(object):
def __init__(self):
"""Classe centralisant différentes méthodes communes aux
SemestreTag, TrajectoireTag, AggregatInterclassTag
"""
pass
# -----------------------------------------------------------------------------------------------------------
def get_all_tags(self):
"""Liste des tags de la table, triée par ordre alphabétique,
extraite des clés du dictionnaire ``moyennes_tags`` connues (tags en doublon
possible).
Returns:
Liste de tags triés par ordre alphabétique
"""
return sorted(list(self.moyennes_tags.keys()))
def df_moyennes_et_classements(self) -> pd.DataFrame:
"""Renvoie un dataframe listant toutes les moyennes,
et les classements des étudiants pour tous les tags.
Est utilisé pour afficher le détail d'un tableau taggué
(semestres, trajectoires ou aggrégat)
Returns:
Le dataframe des notes et des classements
"""
etudiants = self.etudiants
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
tags_tries = self.get_all_tags()
for tag in tags_tries:
moy_tag = self.moyennes_tags[tag]
df = df.join(moy_tag.synthese["notes"].rename(f"Moy {tag}"))
df = df.join(moy_tag.synthese["classements"].rename(f"Class {tag}"))
return df
def df_notes(self) -> pd.DataFrame | None:
"""Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags
Returns:
Un dataframe etudids x tag (avec tag par ordre alphabétique)
"""
tags_tries = self.get_all_tags()
if tags_tries:
dict_series = {}
for tag in tags_tries:
# Les moyennes associés au tag
moy_tag = self.moyennes_tags[tag]
dict_series[tag] = moy_tag.synthese["notes"]
df = pd.DataFrame(dict_series)
return df

View File

@ -6,43 +6,44 @@ from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
class Trajectoire: class Trajectoire:
"""Modélise, pour un aggrégat visé (par ex: 'S2', '3S', '2A')
et un ensemble d'étudiants donnés,
la combinaison des formsemestres des étudiants amenant à un semestre
terminal visé.
Si l'aggrégat est un semestre de type Si, elle stocke le (ou les)
formsemestres de numéro i qu'ont suivis 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 des aggrégats 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, ...
"""
def __init__(self, nom_aggregat: str, semestre_final: FormSemestre): def __init__(self, nom_aggregat: str, semestre_final: FormSemestre):
"""Modélise un ensemble de formsemestres d'étudiants """Modélise un ensemble de formsemestres d'étudiants
amenant à un semestre terminal amenant à un semestre terminal, au sens d'un aggrégat (par ex: 'S2', '3S', '2A').
Si l'aggrégat est un semestre de type Si, elle stocke le (ou 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 des aggrégats 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: Args:
nom_aggregat: Un nom d'aggrégat (par ex: '5S') nom_aggregat: Un nom d'aggrégat (par ex: '5S')
semestre_final: Le semestre final de l'aggrégat semestre_final: Le semestre final de l'aggrégat
""" """
self.nom = nom_aggregat self.nom = nom_aggregat
self.semestre_final = semestre_final """Nom de l'aggrégat"""
self.trajectoire_id = (nom_aggregat, semestre_final.formsemestre_id)
self.formsemestre_final = semestre_final
"""FormSemestre terminal de la trajectoire"""
self.trajectoire_id = (nom_aggregat, semestre_final.formsemestre_id)
"""Identifiant de la trajectoire"""
"""Les semestres à aggréger"""
self.semestres_aggreges = {} self.semestres_aggreges = {}
"""Semestres aggrégés"""
def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]): def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]):
"""Ajoute des semestres au semestre à aggréger """Ajout de semestres aux semestres à aggréger
Args: Args:
semestres: Dictionnaire ``{fid: FormSemestre(fid)} à ajouter`` semestres: Dictionnaire ``{fid: FormSemestre(fid)}`` à ajouter
""" """
self.semestres_aggreges = self.semestres_aggreges | semestres self.semestres_aggreges = self.semestres_aggreges | semestres
@ -55,27 +56,30 @@ class Trajectoire:
semestre = self.semestres_aggreges[fid] semestre = self.semestres_aggreges[fid]
noms.append(f"S{semestre.semestre_id}({fid})") noms.append(f"S{semestre.semestre_id}({fid})")
noms = sorted(noms) noms = sorted(noms)
repr = f"{self.nom} ({self.semestre_final.formsemestre_id}) {self.semestre_final.date_fin.year}" repr = f"{self.nom} ({self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"
if verbose and noms: if verbose and noms:
repr += " - " + "+".join(noms) repr += " - " + "+".join(noms)
return repr return repr
class TrajectoiresJuryPE: class TrajectoiresJuryPE:
"""Centralise toutes les trajectoires du jury PE"""
def __init__(self, annee_diplome: int): def __init__(self, annee_diplome: int):
""" """Classe centralisant toutes les trajectoires des étudiants à prendre
en compte dans un jury PE
Args: Args:
annee_diplome: L'année de diplomation annee_diplome: L'année de diplomation
""" """
self.annee_diplome = annee_diplome self.annee_diplome = annee_diplome
"""Toutes les trajectoires possibles""" """Toutes les trajectoires possibles"""
self.trajectoires: dict[tuple:Trajectoire] = {} self.trajectoires: dict[tuple:Trajectoire] = {}
"""Quelle trajectoires pour quel étudiant : """Ensemble des trajectoires recensées : {(aggregat, fid_terminal): Trajectoire}"""
dictionnaire {etudid: {nom_aggregat: Trajectoire}}"""
self.suivi: dict[int:str] = {} self.suivi: dict[int:str] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque aggrégat,
sa trajectoire : {etudid: {nom_aggregat: Trajectoire}}"""
def cree_trajectoires(self, etudiants: EtudiantsJuryPE): def cree_trajectoires(self, etudiants: EtudiantsJuryPE):
"""Créé toutes les trajectoires, au regard du cursus des étudiants """Créé toutes les trajectoires, au regard du cursus des étudiants
@ -122,9 +126,6 @@ class TrajectoiresJuryPE:
"""Mémoire la trajectoire suivie par l'étudiant""" """Mémoire la trajectoire suivie par l'étudiant"""
self.suivi[etudid][nom_aggregat] = trajectoire self.suivi[etudid][nom_aggregat] = trajectoire
"""Vérifications"""
# dernier_semestre_aggregat = get_dernier_semestre_en_date(semestres_aggreges)
# assert dernier_semestre_aggregat == formsemestre_terminal
def get_trajectoires_etudid(trajectoires, etudid): def get_trajectoires_etudid(trajectoires, etudid):
@ -142,26 +143,3 @@ def get_trajectoires_etudid(trajectoires, etudid):
return liste return liste
def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int):
"""Pour un nom d'aggrégat donné (par ex: 'S3') et un semestre terminal cible
identifié par son formsemestre_id (par ex: 'S3 2022-2023'),
renvoie l'ensemble des semestres à prendre en compte dans
l'aggrégat sous la forme d'un dictionnaire {fid: FormSemestre(fid)}.
Fusionne les cursus individuels des étudiants, dont le cursus correspond
à l'aggrégat visé.
Args:
aggregat: Un aggrégat (par ex. 1A, 2A, 3S, 6S)
formsemestre_id_terminal: L'identifiant du formsemestre terminal de l'aggrégat, devant correspondre au
dernier semestre de l'aggrégat
"""
noms_semestres_aggreges = pe_comp.PARCOURS[aggregat]["aggregat"]
formsemestres = {}
for etudid in self.cursus:
cursus_etudiant = self.cursus[etudid][aggregat]
if formsemestre_id_terminal in cursus_etudiant:
formsemestres_etudiant = cursus_etudiant[formsemestre_id_terminal]
formsemestres = formsemestres | formsemestres_etudiant
return formsemestres

View File

@ -43,7 +43,7 @@ import pandas as pd
import numpy as np import numpy as np
from app.pe.pe_trajectoire import Trajectoire from app.pe.pe_trajectoire import Trajectoire
from app.pe.pe_tabletags import TableTag from app.pe.pe_tabletags import TableTag, MoyenneTag
class TrajectoireTag(TableTag): class TrajectoireTag(TableTag):
@ -58,18 +58,25 @@ class TrajectoireTag(TableTag):
Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S' Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S'
Args:
trajectoire: Une trajectoire (aggrégat+semestre terminal)
semestres_taggues: Les données sur les semestres taggués
""" """
TableTag.__init__(self) TableTag.__init__(self)
# La trajectoire associée
self.trajectoire_id = trajectoire.trajectoire_id self.trajectoire_id = trajectoire.trajectoire_id
"""Identifiant de la trajectoire tagguée"""
self.trajectoire = trajectoire self.trajectoire = trajectoire
"""Trajectoire associée à la trajectoire tagguée"""
# Le nom de la trajectoire tagguée (identique à la trajectoire)
self.nom = self.get_repr() self.nom = self.get_repr()
"""Représentation textuelle de la trajectoire tagguée"""
self.formsemestre_terminal = trajectoire.semestre_final self.formsemestre_terminal = trajectoire.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)
@ -100,11 +107,15 @@ class TrajectoireTag(TableTag):
self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted) self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted)
"""Calcul les moyennes par tag sous forme d'un dataframe""" """Calcul les moyennes par tag sous forme d'un dataframe"""
self.moyennes_tags = {} self.moyennes_tags: dict[str, MoyenneTag] = {}
"""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] moy_gen_tag = self.notes[tag]
self.moyennes_tags[tag] = self.comp_moy_et_stat(moy_gen_tag) self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)
def __eq__(self, other):
"""Egalité de 2 trajectoires tagguées sur la base de leur identifiant"""
return self.trajectoire_id == other.trajectoire_id
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