Finalisation des interclassements

This commit is contained in:
Cléo Baras 2024-02-24 10:48:38 +01:00
parent eff28d64f9
commit 02b057ca5a
5 changed files with 109 additions and 236 deletions

View File

@ -84,12 +84,7 @@ class TableTag(object):
return sorted(tags)
def to_df(
self,
administratif=True,
aggregat=None,
tags_cibles=None,
cohorte=None,
type_colonnes=True,
self, administratif=True, aggregat=None, tags_cibles=None, cohorte=None
) -> pd.DataFrame:
"""Renvoie un dataframe listant toutes les données
des moyennes/classements/nb_inscrits/min/max/moy
@ -136,13 +131,6 @@ class TableTag(object):
for champ in CHAMPS_ADMINISTRATIFS[1:]
] # Nom + Prénom
df = df.sort_values(by=colonnes_tries)
# Conversion des colonnes en multiindex
if type_colonnes:
colonnes = list(df.columns)
colonnes = [tuple(col.split("|")) for col in colonnes]
df.columns = pd.MultiIndex.from_tuples(colonnes)
return df
def has_etuds(self):

View File

@ -41,5 +41,3 @@ def pe_get_log() -> str:
# Affichage dans le tableur pe en cas d'absence de notes
SANS_NOTE = "-"
NOM_STAT_GROUPE = "statistiques du groupe"
NOM_STAT_PROMO = "statistiques de la promo"

View File

@ -52,7 +52,7 @@ import pandas as pd
from app.pe.rcss import pe_rcs
from app.pe.moys import pe_sxtag
from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE
import app.pe.pe_affichage as pe_affichage
import app.pe.pe_etudiant as pe_etudiant
from app.pe.moys import (
@ -116,7 +116,7 @@ class JuryPE(object):
self._gen_xls_rcstags(zipfile)
self._gen_xls_interclasstags(zipfile)
self._gen_xls_synthese_jury_par_tag(zipfile)
# self._gen_xls_synthese_par_etudiant(zipfile)
self._gen_html_synthese_par_etudiant(zipfile)
except Exception as e:
raise e
# et le log
@ -175,6 +175,8 @@ class JuryPE(object):
onglet = res_sem_tag.get_repr(verbose=True)
onglets += []
df = res_sem_tag.to_df()
# Conversion colonnes en multiindex
df = convert_colonnes_to_multiindex(df)
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
@ -235,6 +237,8 @@ class JuryPE(object):
onglet = sxtag.get_repr(verbose=False)
if sxtag.is_significatif():
df = sxtag.to_df()
# Conversion colonnes en multiindex
df = convert_colonnes_to_multiindex(df)
onglets += [onglet]
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
@ -295,6 +299,8 @@ class JuryPE(object):
onglet = rcs_tag.get_repr(verbose=False)
if rcs_tag.is_significatif():
df = rcs_tag.to_df()
# Conversion colonnes en multiindex
df = convert_colonnes_to_multiindex(df)
onglets += [onglet]
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
@ -370,6 +376,8 @@ class JuryPE(object):
onglet = interclass.get_repr()
if interclass.is_significatif():
df = interclass.to_df(cohorte="Promo")
# Conversion colonnes en multiindex
df = convert_colonnes_to_multiindex(df)
onglets += [onglet]
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
@ -387,8 +395,20 @@ class JuryPE(object):
def _gen_xls_synthese_jury_par_tag(self, zipfile: ZipFile):
"""Synthèse des éléments du jury PE tag par tag"""
# Synthèse des éléments du jury PE
self.synthese = self.synthetise_jury_par_tags()
pe_affichage.pe_print(
"*** Synthèse finale des moyennes par tag et par type de moyennes (UEs ou Compétences)***"
)
self.synthese = {}
pe_affichage.pe_print(" -> Synthèse des données administratives")
self.synthese["administratif"] = self.etudiants.df_administratif(
self.diplomes_ids
)
tags = self._do_tags_list(self.interclasstags)
for tag in tags:
for type_moy in [pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_COMPETENCES]:
self.synthese[(tag, type_moy)] = self.df_tag_type(tag, type_moy)
# Export des données => mode 1 seule feuille -> supprimé
pe_affichage.pe_print("*** Export du jury de synthese par tags")
@ -397,36 +417,15 @@ class JuryPE(object):
output, engine="openpyxl"
) as writer:
onglets = []
for (tag, type_moy), df in self.synthese.items():
onglet = f"{tag} {type_moy}"
onglets += [onglet]
# écriture dans l'onglet:
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0)
self.add_file_to_zip(
zipfile, f"synthese_jury_{self.diplome}_par_tag.xlsx", output.read()
)
def _gen_xls_synthese_par_etudiant(self, zipfile: ZipFile):
"""Synthèse des éléments du jury PE, étudiant par étudiant"""
# Synthèse des éléments du jury PE
synthese = self.synthetise_jury_par_etudiants()
# Export des données => mode 1 seule feuille -> supprimé
pe_affichage.pe_print("*** Export du jury de synthese par étudiants")
output = io.BytesIO()
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
onglets = []
for onglet, df in synthese.items():
for onglet, df in self.synthese.items():
# Conversion colonnes en multiindex
df = convert_colonnes_to_multiindex(df)
# Nom de l'onglet
if isinstance(onglet, tuple):
if onglet[1] == pe_moytag.CODE_MOY_COMPETENCES:
nom_onglet = onglet[0][: 31 - 5] + "/Comp."
nom_onglet = onglet[0][: 31 - 7] + " (Comp)"
else:
nom_onglet = onglet[0][: 31 - 3] + "/UE"
nom_onglet = onglet[0][: 31 - 5] + " (UE)"
else:
nom_onglet = onglet
onglets += [nom_onglet]
@ -436,9 +435,21 @@ class JuryPE(object):
output.seek(0)
self.add_file_to_zip(
zipfile, f"synthese_jury_{self.diplome}_par_etudiant.xlsx", output.read()
zipfile, f"synthese_jury_{self.diplome}_par_tag.xlsx", output.read()
)
def _gen_html_synthese_par_etudiant(self, zipfile: ZipFile):
"""Synthèse des éléments du jury PE, étudiant par étudiant"""
# Synthèse des éléments du jury PE
pe_affichage.pe_print("*** Synthèse finale étudiant par étudiant***")
etudids = list(self.diplomes_ids)
for etudid in etudids:
nom, prenom, html = self.synthetise_jury_etudiant(etudid)
self.add_file_to_zip(
zipfile, f"{nom}_{prenom}.html", html, path="etudiants"
)
def _add_log_to_zip(self, zipfile):
"""Add a text file with the log messages"""
log_data = pe_affichage.pe_get_log()
@ -482,26 +493,7 @@ class JuryPE(object):
# Méthodes pour la synthèse du juryPE
# *****************************************************************************************************************
def synthetise_jury_par_tags(self) -> dict[pd.DataFrame]:
"""Synthétise tous les résultats du jury PE dans des dataframes,
dont les onglets sont les tags et des types de calcul de moyennes
(par UEs ou par compétences)"""
pe_affichage.pe_print(
"*** Synthèse finale des moyennes par tag et par type de moyennes (UEs ou Compétences)***"
)
synthese = {}
pe_affichage.pe_print(" -> Synthèse des données administratives")
synthese["administratif"] = self.etudiants.df_administratif(self.diplomes_ids)
tags = self._do_tags_list(self.interclasstags)
for tag in tags:
for type_moy in [pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_COMPETENCES]:
synthese[(tag, type_moy)] = self.df_tag_type(tag, type_moy)
return synthese
def df_tag_type(self, tag, type_moy, type_colonnes=True):
def df_tag_type(self, tag, type_moy):
"""Génère le DataFrame synthétisant les moyennes/classements (groupe +
interclassement promo) pour tous les aggrégats prévus, en fonction
du type (UEs ou Compétences) de données souhaitées,
@ -549,7 +541,6 @@ class JuryPE(object):
aggregat=aggregat,
tags_cibles=[tag],
cohorte="Promo",
type_colonnes=False,
)
if not df_promo.empty:
aff_aggregat += [aggregat]
@ -562,81 +553,31 @@ class JuryPE(object):
)
else:
pe_affichage.pe_print(f" -> Synthèse du tag {tag} par {type_moy} : <vide>")
# Conversion des colonnes en multiindex
if type_colonnes:
colonnes = list(df.columns)
colonnes = [tuple(col.split("|")) for col in colonnes]
df.columns = pd.MultiIndex.from_tuples(colonnes)
return df
# Fin de l'aggrégat
def synthetise_jury_par_etudiants(self) -> dict[pd.DataFrame]:
"""Synthétise tous les résultats du jury PE dans des dataframes,
dont les onglets sont les étudiants"""
pe_affichage.pe_print("*** Synthèse finale des moyennes par étudiants***")
def synthetise_jury_etudiant(self, etudid) -> (str, str, str):
"""Synthétise les résultats d'un étudiant dans un
fichier html à son nom en s'appuyant sur la synthese final
synthese = {}
pe_affichage.pe_print(" -> Synthèse des données administratives")
synthese["administratif"] = self.etudiants.df_administratif(self.diplomes_ids)
Returns:
Un tuple nom, prenom, html
"""
etudiant = self.etudiants.identites[etudid]
nom = etudiant.nom
prenom = etudiant.prenom # initial du prénom
html = "toto"
etudids = list(self.diplomes_ids)
for etudid in etudids:
etudiant = self.etudiants.identites[etudid]
nom = etudiant.nom
prenom = etudiant.prenom[0] # initial du prénom
onglet = f"{nom} {prenom}. ({etudid})"
if len(onglet) > 32: # limite sur la taille des onglets
fin_onglet = f"{prenom}. ({etudid})"
onglet = f"{nom[:32-len(fin_onglet)-2]}." + fin_onglet
pe_affichage.pe_print(f" -> Synthèse de l'étudiant {etudid}")
synthese[onglet] = self.df_synthese_etudiant(etudid)
return synthese
def df_synthese_etudiant(self, etudid: int) -> pd.DataFrame:
"""Créé un DataFrame pour un étudiant donné par son etudid, retraçant
toutes ses moyennes aux différents tag et aggrégats"""
tags = self._do_tags_list(self.interclasstags)
# for onglet, df_synthese in self.synthese.items():
# if isinstance(onglet, tuple): # Les onglets autres que "administratif"
# tag = onglet[0]
# type_moy = onglet[1]
donnees = {}
# colonnes = list(df_synthese.columns)
for tag in tags:
# Une ligne pour le tag
donnees[tag] = {("", "", "tag"): tag}
for aggregat in pe_rcs.TOUS_LES_RCS:
# Le dictionnaire par défaut des moyennes
donnees[tag] |= get_defaut_dict_synthese_aggregat(
aggregat, self.diplome
)
# La trajectoire de l'étudiant sur l'aggrégat
trajectoire = self.rcss_jury.trajectoires_suivies[etudid][aggregat]
if trajectoire:
trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id]
if tag in trajectoire_tagguee.moyennes_tags:
# L'interclassement
interclass = self.interclasstags[aggregat]
# 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
# Construction du dataFrame
df = pd.DataFrame.from_dict(donnees, orient="index")
# Tri par nom/prénom
df.sort_values(by=[("", "", "tag")], inplace=True)
return df
return (nom, prenom, html)
def get_formsemestres_etudiants(etudiants: pe_etudiant.EtudiantsJuryPE) -> dict:
@ -660,102 +601,10 @@ def get_formsemestres_etudiants(etudiants: pe_etudiant.EtudiantsJuryPE) -> dict:
return semestres
def get_defaut_dict_synthese_aggregat(nom_rcs: str, diplome: int) -> dict:
"""Renvoie le dictionnaire de synthèse (à intégrer dans
un tableur excel) pour décrire les résultats d'un aggrégat
Args:
nom_rcs : Le nom du RCS visé
diplôme : l'année du diplôme
"""
# L'affichage de l'aggrégat dans le tableur excel
descr = pe_rcs.get_descr_rcs(nom_rcs)
nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}"
donnees = {
(descr, "", "note"): SANS_NOTE,
# Les stat du groupe
(descr, NOM_STAT_GROUPE, "class."): SANS_NOTE,
(descr, NOM_STAT_GROUPE, "min"): SANS_NOTE,
(descr, NOM_STAT_GROUPE, "moy"): SANS_NOTE,
(descr, NOM_STAT_GROUPE, "max"): SANS_NOTE,
# Les stats de l'interclassement dans la promo
(descr, nom_stat_promo, "class."): SANS_NOTE,
(
descr,
nom_stat_promo,
"min",
): SANS_NOTE,
(
descr,
nom_stat_promo,
"moy",
): SANS_NOTE,
(
descr,
nom_stat_promo,
"max",
): SANS_NOTE,
}
return donnees
def get_dict_synthese_aggregat(
aggregat: str,
trajectoire_tagguee: pe_rcstag.RCSTag,
interclassement_taggue: pe_interclasstag.InterClassTag,
etudid: int,
tag: str,
diplome: int,
):
"""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
à l'aggrégat donné et pour un tag donné"""
donnees = {}
# L'affichage de l'aggrégat dans le tableur excel
descr = pe_rcs.get_descr_rcs(aggregat)
# La note de l'étudiant (chargement à venir)
note = np.nan
# Les données de la trajectoire tagguée pour le tag considéré
moy_tag = trajectoire_tagguee.moyennes_tags[tag]
# Les données de l'étudiant
note = moy_tag.get_note_for_df(etudid)
classement = moy_tag.get_class_for_df(etudid)
nmin = moy_tag.get_min_for_df()
nmax = moy_tag.get_max_for_df()
nmoy = moy_tag.get_moy_for_df()
# Statistiques sur le groupe
if not pd.isna(note) and note != np.nan:
# Les moyennes de cette trajectoire
donnees |= {
(descr, "", "note"): note,
(descr, NOM_STAT_GROUPE, "class."): classement,
(descr, NOM_STAT_GROUPE, "min"): nmin,
(descr, NOM_STAT_GROUPE, "moy"): nmoy,
(descr, NOM_STAT_GROUPE, "max"): nmax,
}
# L'interclassement
moy_tag = interclassement_taggue.moyennes_tags[tag]
classement = moy_tag.get_class_for_df(etudid)
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,
}
return donnees
def convert_colonnes_to_multiindex(df):
"""Convertit les colonnes d'un df pour obtenir des colonnes
multiindex"""
colonnes = list(df.columns)
colonnes = [tuple(col.split("|")) for col in colonnes]
df.columns = pd.MultiIndex.from_tuples(colonnes)
return df

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Service d'un prof{% endblock %}</title>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
<style>
.flash { padding: 8px; color: white; border-radius: 15px; font-weight: bold; }
.success { background-color: limegreen; }
.error { background-color: lightcoral; }
</style>
</head>
<body>
<h1>Résultats PE de {{prenom}} {{nom}}</h1>
<h2>Résultats calculés par UEs</h2>
{% for tag in tags %}
<h3>{{ tag }}</h3>
{% endfor %}
<h2>Résultats calculés par Compétences</h2>
{% for tag in tags %}
<h3>{{ tag }}</h3>
{% endfor %}
</body>
</html>

View File

@ -30,7 +30,7 @@
<p>
Cette fonction génère un ensemble de feuilles de calcul (xlsx)
permettant d'éditer des avis de poursuites d'études pour les étudiants
de BUT diplômés.
de BUT diplômés. Les calculs sous-jacents peuvent prendre un peu de temps (1 à 2 minutes).
<br>
De nombreux aspects sont paramétrables:
<a href="https://scodoc.org/AvisPoursuiteEtudes"
@ -43,7 +43,7 @@
<h3>Avis de poursuites d'études de la promo {{ annee_diplome }}</h3>
<div class="help">
Seront pris en compte les étudiants ayant été inscrits à l'un des semestres suivants :
Seront pris en compte les étudiants ayant (au moins) été inscrits à l'un des semestres suivants :
<ul>
{% for rang in rangs_tries %}