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 141 additions and 514 deletions
Showing only changes of commit cb5df2fffd - Show all commits

View File

@ -1,5 +1,16 @@
from app.models import Formation, FormSemestre from app.models import Formation, FormSemestre
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app import log
PE_DEBUG = 0
if not PE_DEBUG:
# log to notes.log
def pe_print(*a, **kw):
# kw is ignored. log always add a newline
log(" ".join(a))
else:
pe_print = print # print function
def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str: def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str:
@ -29,7 +40,7 @@ def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str:
f"{semestre.date_debut.year}-{semestre.date_fin.year}", f"{semestre.date_debut.year}-{semestre.date_fin.year}",
] ]
if avec_fid: if avec_fid:
description.append(f"({semestre.forsemestre_id})") description.append(f"({semestre.formsemestre_id})")
return " ".join(description) return " ".join(description)

View File

@ -45,21 +45,11 @@ import unicodedata
from flask import g from flask import g
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app import log
from app.models import FormSemestre from app.models import FormSemestre
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc.sco_logos import find_logo from app.scodoc.sco_logos import find_logo
PE_DEBUG = 0
if not PE_DEBUG:
# log to notes.log
def pe_print(*a, **kw):
# kw is ignored. log always add a newline
log(" ".join(a))
else:
pe_print = print # print function
# Generated LaTeX files are encoded as: # Generated LaTeX files are encoded as:

View File

@ -35,10 +35,9 @@ Created on 17/01/2024
@author: barasc @author: barasc
""" """
import app.pe.pe_comp as pe_comp
from app.models import FormSemestre, Identite from app.models import FormSemestre, Identite
from app.pe.pe_comp import pe_print import app.pe.pe_affichage as pe_affichage
import app.pe.pe_comp as pe_comp
class EtudiantsJuryPE: class EtudiantsJuryPE:
@ -86,17 +85,17 @@ class EtudiantsJuryPE:
cosemestres = pe_comp.get_cosemestres_diplomants(self.annee_diplome, None) cosemestres = pe_comp.get_cosemestres_diplomants(self.annee_diplome, None)
self.cosemestres = cosemestres self.cosemestres = cosemestres
pe_comp.pe_print(f"1) Recherche des coSemestres -> {len(cosemestres)} trouvés") pe_affichage.pe_print(f"1) Recherche des coSemestres -> {len(cosemestres)} trouvés")
pe_comp.pe_print("2) Liste des étudiants dans les différents co-semestres") pe_affichage.pe_print("2) Liste des étudiants dans les différents co-semestres")
self.etudiants_ids = get_etudiants_dans_semestres(cosemestres) self.etudiants_ids = get_etudiants_dans_semestres(cosemestres)
pe_comp.pe_print( pe_affichage.pe_print(
" => %d étudiants trouvés dans les cosemestres" % len(self.etudiants_ids) " => %d étudiants trouvés dans les cosemestres" % len(self.etudiants_ids)
) )
# Analyse des parcours étudiants pour déterminer leur année effective de diplome # Analyse des parcours étudiants pour déterminer leur année effective de diplome
# avec prise en compte des redoublements, des abandons, .... # avec prise en compte des redoublements, des abandons, ....
pe_comp.pe_print("3) Analyse des parcours individuels des étudiants") pe_affichage.pe_print("3) Analyse des parcours individuels des étudiants")
no_etud = 0 no_etud = 0
for no_etud, etudid in enumerate(self.etudiants_ids): for no_etud, etudid in enumerate(self.etudiants_ids):
@ -110,10 +109,6 @@ class EtudiantsJuryPE:
# Analyse son parcours pour atteindre chaque semestre de la formation # Analyse son parcours pour atteindre chaque semestre de la formation
self.structure_cursus_etudiant(etudid) self.structure_cursus_etudiant(etudid)
if (no_etud + 1) % 10 == 0:
pe_comp.pe_print(f"{no_etud + 1}")
no_etud += 1
pe_comp.pe_print()
# Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris # Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris
self.etudiants_diplomes = self.get_etudiants_diplomes() self.etudiants_diplomes = self.get_etudiants_diplomes()
@ -126,23 +121,23 @@ class EtudiantsJuryPE:
"""Les formsemestres (des étudiants) dont il faut calculer les moyennes""" """Les formsemestres (des étudiants) dont il faut calculer les moyennes"""
# Synthèse # Synthèse
pe_comp.pe_print( pe_affichage.pe_print(
f" => {len(self.etudiants_diplomes)} étudiants à diplômer en {self.annee_diplome}" f" => {len(self.etudiants_diplomes)} étudiants à diplômer en {self.annee_diplome}"
) )
nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_diplomes) nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_diplomes)
pe_comp.pe_print(f" => {nbre_abandons} étudiants éliminer pour abandon") pe_affichage.pe_print(f" => {nbre_abandons} étudiants non considérés (redoublement, réorientation, abandon")
pe_comp.pe_print( pe_affichage.pe_print(
f" => {len(self.formsemestres_jury_ids)} semestres dont il faut calculer la moyenne" f" => {len(self.formsemestres_jury_ids)} semestres dont il faut calculer la moyenne"
) )
pe_comp.pe_print( # pe_affichage.pe_print(
" => quelques étudiants futurs diplômés : " # " => quelques étudiants futurs diplômés : "
+ ", ".join([str(etudid) for etudid in list(self.etudiants_diplomes)[:10]]) # + ", ".join([str(etudid) for etudid in list(self.etudiants_diplomes)[:10]])
) # )
pe_comp.pe_print( # pe_affichage.pe_print(
" => semestres dont il faut calculer les moyennes : " # " => semestres dont il faut calculer les moyennes : "
+ ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)]) # + ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)])
) # )
# Les abandons : # Les abandons (pour debug)
self.abandons = sorted( self.abandons = sorted(
[ [
cursus["nom"] cursus["nom"]
@ -416,7 +411,7 @@ def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set:
for sem in semestres.values(): # pour chacun des semestres de la liste for sem in semestres.values(): # pour chacun des semestres de la liste
etudiants_du_sem = {ins.etudid for ins in sem.inscriptions} etudiants_du_sem = {ins.etudid for ins in sem.inscriptions}
pe_print(f" --> {sem} : {len(etudiants_du_sem)} etudiants") pe_affichage.pe_print(f" --> {sem} : {len(etudiants_du_sem)} etudiants")
etudiants_ids = ( etudiants_ids = (
etudiants_ids | etudiants_du_sem etudiants_ids | etudiants_du_sem
) # incluant la suppression des doublons ) # incluant la suppression des doublons

View File

@ -1,8 +1,9 @@
from app.comp import moy_sem
from app.pe.pe_tabletags import TableTag from app.pe.pe_tabletags import TableTag
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
from app.comp import moy_sem
import pandas as pd import pandas as pd
import numpy as np import numpy as np

View File

@ -46,29 +46,19 @@ import io
import os import os
from zipfile import ZipFile from zipfile import ZipFile
from app.scodoc.gen_tables import SeqGenTable
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models.etudiants import Identite
from app.scodoc.gen_tables import GenTable, SeqGenTable
import app.scodoc.sco_utils as scu
from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import TrajectoiresJuryPE, Trajectoire from app.pe.pe_trajectoire import TrajectoiresJuryPE, Trajectoire
import app.pe.pe_comp as pe_comp import app.pe.pe_comp as pe_comp
import app.pe.pe_affichage as pe_affichage
from app.pe.pe_semtag import SemestreTag from app.pe.pe_semtag import SemestreTag
from app.pe.pe_interclasstag import AggregatInterclasseTag from app.pe.pe_interclasstag import AggregatInterclasseTag
from app.pe.pe_trajectoiretag import TrajectoireTag from app.pe.pe_trajectoiretag import TrajectoireTag
import app.pe.pe_affichage as pe_affichage import app.pe.pe_affichage as pe_affichage
import pandas as pd import pandas as pd
import numpy as np
# ----------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------
class JuryPE(object): class JuryPE(object):
"""Classe mémorisant toutes les informations nécessaires pour établir un jury de PE. """Classe mémorisant toutes les informations nécessaires pour établir un jury de PE.
Modèle basé sur NotesTable. Modèle basé sur NotesTable.
@ -112,7 +102,7 @@ class JuryPE(object):
self.zipdata = io.BytesIO() self.zipdata = io.BytesIO()
with ZipFile(self.zipdata, "w") as zipfile: with ZipFile(self.zipdata, "w") as zipfile:
# Chargement des étudiants à prendre en compte dans le jury # Chargement des étudiants à prendre en compte dans le jury
pe_comp.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} pour la formation {self.formation_id}""" self.diplome} pour la formation {self.formation_id}"""
) )
@ -123,42 +113,47 @@ class JuryPE(object):
self.diplomes_ids = self.etudiants.diplomes_ids self.diplomes_ids = self.etudiants.diplomes_ids
# Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE # Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE
pe_comp.pe_print("*** Génère les semestres taggués") pe_affichage.pe_print("*** Génère les semestres taggués")
self.semestres_taggues = compute_semestres_tag(self.etudiants) self.semestres_taggues = compute_semestres_tag(self.etudiants)
if pe_comp.PE_DEBUG:
# Intègre le bilan des semestres taggués au zip final # Intègre le bilan des semestres taggués au zip final
output = io.BytesIO()
with pd.ExcelWriter(output, engine="openpyxl") as writer:
for formsemestretag in self.semestres_taggues.values(): for formsemestretag in self.semestres_taggues.values():
filename = formsemestretag.nom.replace(" ", "_") + ".csv" onglet = formsemestretag.nom
pe_comp.pe_print(f" - Export csv de {filename} ") df = formsemestretag.df_semtag()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
output.seek(0)
self.add_file_to_zip( self.add_file_to_zip(
zipfile, zipfile,
filename, f"semestres_taggues_{self.diplome}.xlsx",
formsemestretag.str_tagtable(), output.read(),
path="details_semestres", path="details",
) )
# Génère les trajectoires (combinaison de semestres suivis # Génère les trajectoires (combinaison de semestres suivis
# par un étudiant pour atteindre le semestre final d'un aggrégat) # par un étudiant pour atteindre le semestre final d'un aggrégat)
pe_comp.pe_print( pe_affichage.pe_print(
"*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants" "*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants"
) )
self.trajectoires = TrajectoiresJuryPE(self.diplome) self.trajectoires = TrajectoiresJuryPE(self.diplome)
self.trajectoires.cree_trajectoires(self.etudiants) self.trajectoires.cree_trajectoires(self.etudiants)
# Génère les moyennes par tags des trajectoires # Génère les moyennes par tags des trajectoires
pe_comp.pe_print( pe_affichage.pe_print(
"*** Calcule les moyennes par tag des trajectoires possibles" "*** Calcule les moyennes par tag des trajectoires possibles"
) )
self.trajectoires_tagguees = compute_trajectoires_tag( self.trajectoires_tagguees = compute_trajectoires_tag(
self.trajectoires, self.etudiants, self.semestres_taggues self.trajectoires, self.etudiants, self.semestres_taggues
) )
if pe_comp.PE_DEBUG: if pe_affichage.PE_DEBUG:
# Intègre le bilan des trajectoires tagguées au zip final # Intègre le bilan des trajectoires tagguées au zip final
for trajectoire_tagguee in self.trajectoires_tagguees.values(): for trajectoire_tagguee in self.trajectoires_tagguees.values():
filename = trajectoire_tagguee.get_repr().replace(" ", "_") + ".csv" filename = trajectoire_tagguee.get_repr().replace(" ", "_") + ".csv"
pe_comp.pe_print(f" - Export csv de {filename} ") # pe_affichage.pe_print(f" - Export csv de {filename} ")
self.add_file_to_zip( self.add_file_to_zip(
zipfile, zipfile,
filename, filename,
@ -167,16 +162,16 @@ class JuryPE(object):
) )
# Génère les interclassements (par promo et) par (nom d') aggrégat # Génère les interclassements (par promo et) par (nom d') aggrégat
pe_comp.pe_print("*** Génère les interclassements par aggrégat") pe_affichage.pe_print("*** Génère les interclassements par aggrégat")
self.interclassements_taggues = compute_interclassements( self.interclassements_taggues = compute_interclassements(
self.etudiants, self.trajectoires, self.trajectoires_tagguees self.etudiants, self.trajectoires, self.trajectoires_tagguees
) )
if pe_comp.PE_DEBUG: if pe_affichage.PE_DEBUG:
# Intègre le bilan des aggrégats (par promo) au zip final # Intègre le bilan des aggrégats (par promo) au zip final
for interclass_tag in self.interclassements_taggues.values(): for interclass_tag in self.interclassements_taggues.values():
filename = interclass_tag.get_repr().replace(" ", "_") + ".csv" filename = interclass_tag.get_repr().replace(" ", "_") + ".csv"
pe_comp.pe_print(f" - Export csv de {filename} ") # pe_affichage.pe_print(f" - Export csv de {filename} ")
self.add_file_to_zip( self.add_file_to_zip(
zipfile, zipfile,
filename, filename,
@ -188,7 +183,7 @@ class JuryPE(object):
self.synthese = self.synthetise_juryPE() self.synthese = self.synthetise_juryPE()
# Export des données => mode 1 seule feuille -> supprimé # Export des données => mode 1 seule feuille -> supprimé
pe_comp.pe_print("*** Export du jury de synthese") pe_affichage.pe_print("*** Export du jury de synthese")
output = io.BytesIO() output = io.BytesIO()
with pd.ExcelWriter(output, engine="openpyxl") as writer: with pd.ExcelWriter(output, engine="openpyxl") as writer:
@ -241,15 +236,15 @@ class JuryPE(object):
def synthetise_juryPE(self): def synthetise_juryPE(self):
"""Synthétise tous les résultats du jury PE dans des dataframes""" """Synthétise tous les résultats du jury PE dans des dataframes"""
pe_comp.pe_print("*** Synthèse finale des moyennes ***") pe_affichage.pe_print("*** Synthèse finale des moyennes ***")
synthese = {} synthese = {}
pe_comp.pe_print(" -> Synthèse des données administratives") pe_affichage.pe_print(" -> Synthèse des données administratives")
synthese["administratif"] = self.df_administratif() synthese["administratif"] = self.df_administratif()
tags = self.do_tags_list(self.interclassements_taggues) tags = self.do_tags_list(self.interclassements_taggues)
for tag in tags: for tag in tags:
pe_comp.pe_print(f" -> Synthèse du tag {tag}") pe_affichage.pe_print(f" -> Synthèse du tag {tag}")
synthese[tag] = self.df_tag(tag) synthese[tag] = self.df_tag(tag)
return synthese return synthese
@ -325,7 +320,7 @@ class JuryPE(object):
donnees[etudid] |= { donnees[etudid] |= {
f"{aggregat} notes ": "-", f"{aggregat} notes ": "-",
f"{aggregat} class. (groupe)": "-", f"{aggregat} class. (groupe)": "-",
f"{aggregat} min/moy/max (groupe)": "-", f"{aggregat} min | moy | max (groupe)": "-",
} }
if trajectoire: if trajectoire:
trajectoire_tagguee = self.trajectoires_tagguees[ trajectoire_tagguee = self.trajectoires_tagguees[
@ -335,24 +330,23 @@ class JuryPE(object):
bilan = trajectoire_tagguee.moyennes_tags[tag] bilan = trajectoire_tagguee.moyennes_tags[tag]
donnees[etudid] |= { donnees[etudid] |= {
f"{aggregat} notes ": round(bilan['notes'].loc[etudid], 2), f"{aggregat} notes ": round(bilan["notes"].loc[etudid], 2),
f"{aggregat} class. (groupe)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}", f"{aggregat} class. (groupe)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
f"{aggregat} min/moy/max (groupe)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}", f"{aggregat} min | moy | max (groupe)": f"{bilan['min']:.1f} | {bilan['moy']:.1f} | {bilan['max']:.1f}",
} }
"""L'interclassement""" """L'interclassement"""
interclass = self.interclassements_taggues[aggregat] interclass = self.interclassements_taggues[aggregat]
donnees[etudid] |= { donnees[etudid] |= {
f"{aggregat} class. (promo)": "-", f"{aggregat} class. (promo)": "-",
f"{aggregat} min/moy/max (promo)": "-", f"{aggregat} min | moy | max (promo)": "-",
} }
if tag in interclass.moyennes_tags: if tag in interclass.moyennes_tags:
bilan = interclass.moyennes_tags[tag] bilan = interclass.moyennes_tags[tag]
donnees[etudid] |= { donnees[etudid] |= {
f"{aggregat} class. (promo)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}", f"{aggregat} class. (promo)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
f"{aggregat} min/moy/max (promo)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}", f"{aggregat} min | moy | max (promo)": f"{bilan['min']:.1f} | {bilan['moy']:.1f} | {bilan['max']:.1f}",
} }
# Fin de l'aggrégat # Fin de l'aggrégat
@ -390,7 +384,7 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
""" """
"""Création des semestres taggués, de type 'S1', 'S2', ...""" """Création des semestres taggués, de type 'S1', 'S2', ..."""
pe_comp.pe_print("*** Création des semestres taggués") pe_affichage.pe_print("*** Création des semestres taggués")
formsemestres = etudiants.get_formsemestres( formsemestres = etudiants.get_formsemestres(
semestres_recherches=pe_comp.TOUS_LES_SEMESTRES semestres_recherches=pe_comp.TOUS_LES_SEMESTRES
@ -398,19 +392,11 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
semestres_tags = {} semestres_tags = {}
for frmsem_id, formsemestre in formsemestres.items(): for frmsem_id, formsemestre in formsemestres.items():
"""Choix d'un nom pour le semestretag"""
nom = "S%d %d %d-%d" % (
formsemestre.semestre_id,
frmsem_id,
formsemestre.date_debut.year,
formsemestre.date_fin.year,
)
pe_comp.pe_print(f" --> Semestre taggué {nom} sur la base de {formsemestre}")
# Crée le semestre_tag et exécute les calculs de moyennes # Crée le semestre_tag et exécute les calculs de moyennes
formsemestretag = SemestreTag(nom, frmsem_id) formsemestretag = SemestreTag(frmsem_id)
pe_affichage.pe_print(
f" --> Semestre taggué {formsemestretag.nom} sur la base de {formsemestre}"
)
# Stocke le semestre taggué # Stocke le semestre taggué
semestres_tags[frmsem_id] = formsemestretag semestres_tags[frmsem_id] = formsemestretag
@ -443,23 +429,16 @@ def compute_trajectoires_tag(
semestres_tag: Les semestres tag (pour lesquels des moyennes par tag ont été calculés) semestres_tag: Les semestres tag (pour lesquels des moyennes par tag ont été calculés)
Return: Return:
Un dictionnaire de la forme {nom_aggregat: {fid_terminal: SetTag(fid_terminal)} } Un dictionnaire de la forme ``{nom_aggregat: {fid_terminal: SetTag(fid_terminal)} }``
""" """
pe_comp.pe_print(" *** Création des aggrégats ")
trajectoires_tagguees = {} trajectoires_tagguees = {}
for trajectoire_id in trajectoires.trajectoires: for trajectoire_id, trajectoire in trajectoires.trajectoires.items():
trajectoire = trajectoires.trajectoires[trajectoire_id]
nom = trajectoire.get_repr() nom = trajectoire.get_repr()
pe_affichage.pe_print(f" --> Aggrégat {nom}")
pe_comp.pe_print(f" --> Fusion {nom}") # Trajectoire_tagguee associée
trajectoire_tagguee = TrajectoireTag(nom, trajectoire, semestres_taggues) trajectoire_tagguee = TrajectoireTag(nom, trajectoire, semestres_taggues)
"""Trajectoire_tagguee associée""" # Mémorise le résultat
"""Mémorise le résultat"""
trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee
return trajectoires_tagguees return trajectoires_tagguees
@ -474,11 +453,9 @@ def compute_interclassements(
pour fournir un classement sur la promo. Le classement est établi au regard du nombre pour fournir un classement sur la promo. Le classement est établi au regard du nombre
d'étudiants ayant participé au même aggrégat. d'étudiants ayant participé au même aggrégat.
""" """
pe_comp.pe_print(" Interclassement sur la promo")
aggregats_interclasses_taggues = {} aggregats_interclasses_taggues = {}
for nom_aggregat in pe_comp.TOUS_LES_SEMESTRES + pe_comp.TOUS_LES_AGGREGATS: for nom_aggregat in pe_comp.TOUS_LES_SEMESTRES + pe_comp.TOUS_LES_AGGREGATS:
pe_comp.pe_print(f" --> {nom_aggregat}") pe_affichage.pe_print(f" --> Interclassement {nom_aggregat}")
interclass = AggregatInterclasseTag( interclass = AggregatInterclasseTag(
nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees
) )

View File

@ -45,61 +45,61 @@ 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_comp as pe_comp 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
import pandas as pd
class SemestreTag(TableTag): class SemestreTag(TableTag):
"""Un SemestreTag représente les résultats des étudiants à un semestre, en donnant
def __init__(self, formsemestre_id: int):
"""
Un SemestreTag représente les résultats des étudiants à un semestre, en donnant
accès aux moyennes par tag. accès aux moyennes par tag.
Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT. Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT.
"""
# -----------------------------------------------------------------------------
# Fonctions d'initialisation
# -----------------------------------------------------------------------------
def __init__(self, nom: str, formsemestre_id: int):
"""
Args: Args:
nom: Nom à donner au SemestreTag nom: Nom à donner au SemestreTag
formsemestre_id: Identifiant du FormSemestre sur lequel il se base formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base
""" """
TableTag.__init__(self, nom=nom) # TableTag.__init__(self, nom=nom)
"""Le semestre""" # Le semestre
self.formsemestre_id = formsemestre_id self.formsemestre_id = formsemestre_id
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id) self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
"""Les résultats du semestre""" # Le nom du semestre taggué
self.nom = self.get_repr()
# Les résultats du semestre
self.nt = load_formsemestre_results(self.formsemestre) self.nt = load_formsemestre_results(self.formsemestre)
"""Les étudiants""" # Les étudiants
self.etuds = self.nt.etuds self.etuds = self.nt.etuds
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds} self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
"""Les notes, les modules implémentés triés, les étudiants, les coeffs, # Les notes, les modules implémentés triés, les étudiants, les coeffs,
récupérés notamment de py:mod:`res_but` # récupérés notamment de py:mod:`res_but`
"""
self.sem_cube = self.nt.sem_cube self.sem_cube = self.nt.sem_cube
self.modimpls_sorted = self.nt.formsemestre.modimpls_sorted self.modimpls_sorted = self.nt.formsemestre.modimpls_sorted
self.modimpl_coefs_df = self.nt.modimpl_coefs_df self.modimpl_coefs_df = self.nt.modimpl_coefs_df
"""Les inscriptions au module et les dispenses d'UE""" # Les inscriptions au module et les dispenses d'UE
self.modimpl_inscr_df = self.nt.modimpl_inscr_df self.modimpl_inscr_df = self.nt.modimpl_inscr_df
self.ues = self.nt.ues self.ues = self.nt.ues
self.ues_inscr_parcours_df = self.nt.load_ues_inscr_parcours() self.ues_inscr_parcours_df = self.nt.load_ues_inscr_parcours()
self.dispense_ues = self.nt.dispense_ues self.dispense_ues = self.nt.dispense_ues
"""Les tags (en supprimant les tags réservés)""" # Les tags (en supprimant les tags réservés)
self.tags = get_synthese_tags_semestre(self.nt.formsemestre) self.tags = get_synthese_tags_semestre(self.nt.formsemestre)
for tag in TAGS_RESERVES: for tag in TAGS_RESERVES:
if tag in self.tags: if tag in self.tags:
del self.tags[tag] del self.tags[tag]
"""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: for tag in self.tags:
pe_comp.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)
class_gen_tag = moy_sem.comp_ranks_series(moy_gen_tag)[1] # en int class_gen_tag = moy_sem.comp_ranks_series(moy_gen_tag)[1] # en int
self.moyennes_tags[tag] = { self.moyennes_tags[tag] = {
@ -111,8 +111,7 @@ class SemestreTag(TableTag):
"nb_inscrits": len(moy_gen_tag), "nb_inscrits": len(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é
pe_comp.pe_print(f" -> Traitement du tag but")
moy_gen_but = self.nt.etud_moy_gen moy_gen_but = self.nt.etud_moy_gen
class_gen_but = self.nt.etud_moy_gen_ranks_int class_gen_but = self.nt.etud_moy_gen_ranks_int
self.moyennes_tags["but"] = { self.moyennes_tags["but"] = {
@ -124,16 +123,18 @@ class SemestreTag(TableTag):
"nb_inscrits": len(moy_gen_but), "nb_inscrits": len(moy_gen_but),
} }
"""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) # les tags par ordre alphabétique self.tags_sorted = sorted(self.moyennes_tags) # les tags par ordre alphabétique
self.notes = self.df_tagtable() # Le dataframe synthétique des notes (=moyennes par tag) self.notes = (
self.df_tagtable()
) # Le dataframe synthétique des notes (=moyennes par tag)
# ----------------------------------------------------------------------------- pe_affichage.pe_print(f" => Traitement des tags {', '.join(self.tags_sorted)}")
def get_etudids(self):
"""Renvoie la liste des etud_id des étudiants inscrits au semestre""" def get_repr(self):
return [etud["etudid"] for etud in self.inscrlist] """Nom affiché pour le semestre taggué"""
return pe_affichage.nom_semestre_etape(self.formsemestre, avec_fid=True)
# -----------------------------------------------------------------------------
def compute_moyenne_tag(self, tag: str) -> list: def compute_moyenne_tag(self, tag: str) -> list:
"""Calcule la moyenne des étudiants pour le tag indiqué, """Calcule la moyenne des étudiants pour le tag indiqué,
pour ce SemestreTag. pour ce SemestreTag.
@ -192,14 +193,22 @@ class SemestreTag(TableTag):
return moy_gen_tag return moy_gen_tag
# -----------------------------------------------------------------------------
def get_noteEtCoeff_modimpl(self, modimpl_id, etudid, profondeur=2): def get_noteEtCoeff_modimpl(self, modimpl_id, etudid, profondeur=2):
"""Renvoie un couple donnant la note et le coeff normalisé d'un étudiant à un module d'id modimpl_id. """Renvoie un couple donnant la note et le coeff normalisé d'un étudiant à un module d'id modimpl_id.
La note et le coeff sont extraits : La note et le coeff sont extraits :
1) soit des données du semestre en normalisant le coefficient par rapport à la somme des coefficients des modules du semestre, 1) soit des données du semestre en normalisant le coefficient par rapport à la somme des coefficients des modules du semestre,
2) soit des données des UE précédemment capitalisées, en recherchant un module de même CODE que le modimpl_id proposé, 2) soit des données des UE précédemment capitalisées, en recherchant un module de même CODE que le modimpl_id proposé,
le coefficient normalisé l'étant alors par rapport au total des coefficients du semestre auquel appartient l'ue capitalisée le coefficient normalisé l'étant alors par rapport au total des coefficients du semestre auquel appartient l'ue capitalisée
TODO:: A rependre si nécessaire
""" """
def get_ue_capitalisees(etudid) -> list[dict]:
"""Renvoie la liste des capitalisation effectivement capitalisées par un étudiant"""
if etudid in self.nt.validations.ue_capitalisees.index:
return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records")
return []
(note, coeff_norm) = (None, None) (note, coeff_norm) = (None, None)
modimpl = get_moduleimpl(modimpl_id) # Le module considéré modimpl = get_moduleimpl(modimpl_id) # Le module considéré
@ -207,7 +216,7 @@ class SemestreTag(TableTag):
return (None, None) return (None, None)
# Y-a-t-il eu capitalisation d'UE ? # Y-a-t-il eu capitalisation d'UE ?
ue_capitalisees = self.get_ue_capitalisees( ue_capitalisees = get_ue_capitalisees(
etudid etudid
) # les ue capitalisées des étudiants ) # les ue capitalisées des étudiants
ue_capitalisees_id = { ue_capitalisees_id = {
@ -273,130 +282,18 @@ class SemestreTag(TableTag):
# Sinon - pas de notes à prendre en compte # Sinon - pas de notes à prendre en compte
return (note, coeff_norm) return (note, coeff_norm)
# ----------------------------------------------------------------------------- def df_semtag(self):
def get_ue_capitalisees(self, etudid) -> list[dict]: """Renvoie un dataframe listant toutes les moyennes,
"""Renvoie la liste des capitalisation effectivement capitalisées par un étudiant""" les rangs des étudiants pour tous les tags dans le semestre taggué"""
if etudid in self.nt.validations.ue_capitalisees.index:
return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records")
return []
# ----------------------------------------------------------------------------- etudiants = self.etudiants
# Fonctions d'affichage (et d'export csv) des données du semestre en mode debug df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
# -----------------------------------------------------------------------------
def str_detail_resultat_d_un_tag(self, tag, etudid=None, delim=";"):
"""Renvoie une chaine de caractère décrivant les résultats d'étudiants à un tag :
rappelle les notes obtenues dans les modules à prendre en compte, les moyennes et les rangs calculés.
Si etudid=None, tous les étudiants inscrits dans le semestre sont pris en compte. Sinon seuls les étudiants indiqués sont affichés.
"""
# Entete
chaine = delim.join(["%15s" % "nom", "etudid"]) + delim
taglist = self.get_all_tags()
if tag in taglist:
for mod in self.tagdict[tag].values():
chaine += mod["module_code"] + delim
chaine += ("%1.1f" % mod["ponderation"]) + delim
chaine += "coeff" + delim
chaine += delim.join(
["moyenne", "rang", "nbinscrit", "somme_coeff", "somme_coeff"]
) # ligne 1
chaine += "\n"
# Différents cas de boucles sur les étudiants (de 1 à plusieurs) for tag in self.get_all_tags():
if etudid == None: df = df.join(self.moyennes_tags[tag]["notes"].rename(f"Moy {tag}"))
lesEtuds = self.get_etudids() df = df.join(self.moyennes_tags[tag]["classements"].rename(f"Class {tag}"))
elif isinstance(etudid, str) and etudid in self.get_etudids():
lesEtuds = [etudid]
elif isinstance(etudid, list):
lesEtuds = [eid for eid in self.get_etudids() if eid in etudid]
else:
lesEtuds = []
for etudid in lesEtuds:
descr = (
"%15s" % self.nt.get_nom_short(etudid)[:15]
+ delim
+ str(etudid)
+ delim
)
if tag in taglist:
for modimpl_id in self.tagdict[tag]:
(note, coeff) = self.get_noteEtCoeff_modimpl(modimpl_id, etudid)
descr += (
(
"%2.2f" % note
if note != None and isinstance(note, float)
else str(note)
)
+ delim
+ (
"%1.5f" % coeff
if coeff != None and isinstance(coeff, float)
else str(coeff)
)
+ delim
+ (
"%1.5f" % (coeff * self.somme_coeffs)
if coeff != None and isinstance(coeff, float)
else "???" # str(coeff * self._sum_coeff_semestre) # voir avec Cléo
)
+ delim
)
moy = self.get_moy_from_resultats(tag, etudid)
rang = self.get_rang_from_resultats(tag, etudid)
coeff = self.get_coeff_from_resultats(tag, etudid)
tot = (
coeff * self.somme_coeffs
if coeff != None
and self.somme_coeffs != None
and isinstance(coeff, float)
else None
)
descr += (
pe_tagtable.TableTag.str_moytag(
moy, rang, len(self.get_etudids()), delim=delim
)
+ delim
)
descr += (
(
"%1.5f" % coeff
if coeff != None and isinstance(coeff, float)
else str(coeff)
)
+ delim
+ (
"%.2f" % (tot)
if tot != None
else str(coeff) + "*" + str(self.somme_coeffs)
)
)
chaine += descr
chaine += "\n"
return chaine
def str_tagsModulesEtCoeffs(self):
"""Renvoie une chaine affichant la liste des tags associés au semestre,
les modules qui les concernent et les coeffs de pondération.
Plus concrètement permet d'afficher le contenu de self._tagdict"""
chaine = "Semestre %s d'id %d" % (self.nom, id(self)) + "\n"
chaine += " -> somme de coeffs: " + str(self.somme_coeffs) + "\n"
taglist = self.get_all_tags()
for tag in taglist:
chaine += " > " + tag + ": "
for modid, mod in self.tagdict[tag].items():
chaine += (
mod["module_code"]
+ " ("
+ str(mod["coeff"])
+ "*"
+ str(mod["ponderation"])
+ ") "
+ str(modid)
+ ", "
)
chaine += "\n"
return chaine
return df
# ************************************************************************ # ************************************************************************
# Fonctions diverses # Fonctions diverses
@ -437,8 +334,8 @@ def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float:
def get_synthese_tags_semestre(formsemestre: FormSemestre): def get_synthese_tags_semestre(formsemestre: FormSemestre):
"""Etant données les implémentations des modules du semestre (modimpls), """Etant données les implémentations des modules du semestre (modimpls),
synthétise les tags les concernant (tags saisis dans le programme pédagogique) synthétise les tags les concernant (tags saisis dans le programme pédagogique)
en les associant aux modimpls qui les concernent (modimpl_id, module_id, en les associant aux modimpls qui les concernent (modimpl_id) et
le code du module, coeff et pondération fournie avec le tag (par défaut 1 si non indiquée)). aucoeff et pondération fournie avec le tag (par défaut 1 si non indiquée)).
{ tagname1: { modimpl_id1: { 'module_id': ..., { tagname1: { modimpl_id1: { 'module_id': ...,
'coeff': ..., 'coeff': ...,
@ -451,6 +348,9 @@ def get_synthese_tags_semestre(formsemestre: FormSemestre):
Args: Args:
formsemestre: Le formsemestre à la base de la recherche des tags formsemestre: Le formsemestre à la base de la recherche des tags
Return:
Un dictionnaire de tags
""" """
synthese_tags = {} synthese_tags = {}

View File

@ -91,188 +91,6 @@ class TableTag(object):
return sorted(self.moyennes_tags.keys()) return sorted(self.moyennes_tags.keys())
# *****************************************************************************************************************
# Accesseurs
# *****************************************************************************************************************
# -----------------------------------------------------------------------------------------------------------
def get_moy_from_resultats(self, tag, etudid):
"""Renvoie la moyenne obtenue par un étudiant à un tag donné au regard du format de self.resultats"""
return (
self.moyennes_tags[tag][etudid][0]
if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
else None
)
# -----------------------------------------------------------------------------------------------------------
def get_rang_from_resultats(self, tag, etudid):
"""Renvoie le rang à un tag d'un étudiant au regard du format de self.resultats"""
return (
self.rangs[tag][etudid]
if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
else None
)
# -----------------------------------------------------------------------------------------------------------
def get_coeff_from_resultats(self, tag, etudid):
"""Renvoie la somme des coeffs de pondération normalisée utilisés dans le calcul de la moyenne à un tag d'un étudiant
au regard du format de self.resultats.
"""
return (
self.moyennes_tags[tag][etudid][1]
if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
else None
)
# -----------------------------------------------------------------------------------------------------------
def get_nbinscrits(self):
"""Renvoie le nombre d'inscrits"""
return len(self.inscrlist)
# -----------------------------------------------------------------------------------------------------------
def get_moy_from_stats(self, tag):
"""Renvoie la moyenne des notes calculées pour d'un tag donné"""
return self.statistiques[tag][0] if tag in self.statistiques else None
def get_min_from_stats(self, tag):
"""Renvoie la plus basse des notes calculées pour d'un tag donné"""
return self.statistiques[tag][1] if tag in self.statistiques else None
def get_max_from_stats(self, tag):
"""Renvoie la plus haute des notes calculées pour d'un tag donné"""
return self.statistiques[tag][2] if tag in self.statistiques else None
# -----------------------------------------------------------------------------------------------------------
# La structure des données mémorisées pour chaque tag dans le dictionnaire de synthèse
# d'un jury PE
FORMAT_DONNEES_ETUDIANTS = (
"note",
"coeff",
"rang",
"nbinscrits",
"moy",
"max",
"min",
)
def get_resultatsEtud(self, tag, etudid):
"""Renvoie un tuple (note, coeff, rang, nb_inscrit, moy, min, max) synthétisant les résultats d'un étudiant
à un tag donné. None sinon"""
return (
self.get_moy_from_resultats(tag, etudid),
self.get_coeff_from_resultats(tag, etudid),
self.get_rang_from_resultats(tag, etudid),
self.get_nbinscrits(),
self.get_moy_from_stats(tag),
self.get_min_from_stats(tag),
self.get_max_from_stats(tag),
)
# return self.tag_stats[tag]
# else :
# return self.pe_stats
# *****************************************************************************************************************
# Ajout des notes
# *****************************************************************************************************************
# -----------------------------------------------------------------------------------------------------------
def add_moyennesTag(self, tag, listMoyEtCoeff) -> bool:
"""
Mémorise les moyennes, les coeffs de pondération et les etudid dans resultats
avec calcul du rang
:param tag: Un tag
:param listMoyEtCoeff: Une liste donnant [ (moy, coeff, etudid) ]
TODO:: Inutile maintenant ?
"""
# ajout des moyennes au dictionnaire résultat
if listMoyEtCoeff:
self.moyennes_tags[tag] = {
etudid: (moyenne, somme_coeffs)
for (moyenne, somme_coeffs, etudid) in listMoyEtCoeff
}
# Calcule les rangs
lesMoyennesTriees = sorted(
listMoyEtCoeff,
reverse=True,
key=lambda col: col[0]
if isinstance(col[0], float)
else 0, # remplace les None et autres chaines par des zéros
) # triées
self.rangs[tag] = scu.comp_ranks(lesMoyennesTriees) # les rangs
# calcul des stats
self.comp_stats_d_un_tag(tag)
return True
return False
# *****************************************************************************************************************
# Méthodes dévolues aux calculs de statistiques (min, max, moy) sur chaque moyenne taguée
# *****************************************************************************************************************
def comp_stats_d_un_tag(self, tag):
"""
Calcule la moyenne generale, le min, le max pour un tag donné,
en ne prenant en compte que les moyennes significatives. Mémorise le resultat dans
self.statistiques
"""
stats = ("-NA-", "-", "-")
if tag not in self.moyennes_tags:
return stats
notes = [
self.get_moy_from_resultats(tag, etudid)
for etudid in self.moyennes_tags[tag]
] # les notes du tag
notes_valides = [
note for note in notes if isinstance(note, float) and note != None
]
nb_notes_valides = len(notes_valides)
if nb_notes_valides > 0:
(moy, _) = moyenne_ponderee_terme_a_terme(notes_valides, force=True)
self.statistiques[tag] = (moy, max(notes_valides), min(notes_valides))
# ************************************************************************
# Méthodes dévolues aux affichages -> a revoir
# ************************************************************************
def str_resTag_d_un_etudiant(self, tag, etudid, delim=";"):
"""Renvoie une chaine de caractères (valable pour un csv)
décrivant la moyenne et le rang d'un étudiant, pour un tag donné ;
"""
if tag not in self.get_all_tags() or etudid not in self.moyennes_tags[tag]:
return ""
moystr = TableTag.str_moytag(
self.get_moy_from_resultats(tag, etudid),
self.get_rang_from_resultats(tag, etudid),
self.get_nbinscrits(),
delim=delim,
)
return moystr
def str_res_d_un_etudiant(self, etudid, delim=";"):
"""Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique)."""
return delim.join(
[self.str_resTag_d_un_etudiant(tag, etudid) for tag in self.get_all_tags()]
)
# -----------------------------------------------------------------------
def str_moytag(cls, moyenne, rang, nbinscrit, delim=";"):
"""Renvoie une chaine de caractères représentant une moyenne (float ou string) et un rang
pour différents formats d'affichage : HTML, debug ligne de commande, csv"""
moystr = (
"%2.2f%s%s%s%d" % (moyenne, delim, rang, delim, nbinscrit)
if isinstance(moyenne, float)
else str(moyenne) + delim + str(rang) + delim + str(nbinscrit)
)
return moystr
str_moytag = classmethod(str_moytag)
# -----------------------------------------------------------------------
def df_tagtable(self): def df_tagtable(self):
"""Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags """Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags
@ -300,68 +118,3 @@ class TableTag(object):
return df.to_csv(sep=";") return df.to_csv(sep=";")
# ************************************************************************
# Fonctions diverses
# ************************************************************************
# *********************************************
def moyenne_ponderee_terme_a_terme(notes, coefs=None, force=False):
"""
Calcule la moyenne pondérée d'une liste de notes avec d'éventuels coeffs de pondération.
Renvoie le résultat sous forme d'un tuple (moy, somme_coeff)
La liste de notes contient soit :
1) des valeurs numériques
2) des strings "-NA-" (pas de notes) ou "-NI-" (pas inscrit) ou "-c-" ue capitalisée,
3) None.
Le paramètre force indique si le calcul de la moyenne doit être forcée ou non, c'est à
dire s'il y a ou non omission des notes non numériques (auquel cas la moyenne est
calculée sur les notes disponibles) ; sinon renvoie (None, None).
"""
# Vérification des paramètres d'entrée
if not isinstance(notes, list) or (
coefs != None and not isinstance(coefs, list) and len(coefs) != len(notes)
):
raise ValueError("Erreur de paramètres dans moyenne_ponderee_terme_a_terme")
# Récupération des valeurs des paramètres d'entrée
coefs = [1] * len(notes) if coefs is None else coefs
# S'il n'y a pas de notes
if not notes: # Si notes = []
return (None, None)
# Liste indiquant les notes valides
notes_valides = [
(isinstance(note, float) and not np.isnan(note)) or isinstance(note, int)
for note in notes
]
# Si on force le calcul de la moyenne ou qu'on ne le force pas
# et qu'on a le bon nombre de notes
if force or sum(notes_valides) == len(notes):
moyenne, ponderation = 0.0, 0.0
for i in range(len(notes)):
if notes_valides[i]:
moyenne += coefs[i] * notes[i]
ponderation += coefs[i]
return (
(moyenne / (ponderation * 1.0), ponderation)
if ponderation != 0
else (None, 0)
)
# Si on ne force pas le calcul de la moyenne
return (None, None)
# -------------------------------------------------------------------------------------------
def conversionDate_StrToDate(date_fin):
"""Conversion d'une date fournie sous la forme d'une chaine de caractère de
type 'jj/mm/aaaa' en un objet date du package datetime.
Fonction servant au tri des semestres par date
"""
(d, m, y) = [int(x) for x in date_fin.split("/")]
date_fin_dst = datetime.date(y, m, d)
return date_fin_dst

View File

@ -1,4 +1,6 @@
import app.pe.pe_comp as pe_comp import app.pe.pe_comp as pe_comp
import app.pe.pe_affichage as pe_affichage
from app.models import FormSemestre from app.models import FormSemestre
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
@ -129,7 +131,7 @@ def get_trajectoires_etudid(trajectoires, etudid):
trajectoires suivies par un étudiant trajectoires suivies par un étudiant
""" """
if etudid not in trajectoires.suivi: if etudid not in trajectoires.suivi:
pe_comp.pe_print(f"{etudid} fait-il bien partie du jury ?") pe_affichage.pe_print(f"{etudid} fait-il bien partie du jury ?")
liste = [] liste = []
for aggregat in pe_comp.TOUS_LES_PARCOURS: for aggregat in pe_comp.TOUS_LES_PARCOURS:

View File

@ -39,12 +39,10 @@ Created on Fri Sep 9 09:15:05 2016
from app.comp import moy_sem from app.comp import moy_sem
from app.comp.res_sem import load_formsemestre_results from app.comp.res_sem import load_formsemestre_results
from app.pe.pe_semtag import SemestreTag from app.pe.pe_semtag import SemestreTag
from app.pe import pe_tabletags
import pandas as pd 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_etudiant import EtudiantsJuryPE
from app.pe.pe_tabletags import TableTag from app.pe.pe_tabletags import TableTag