forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -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):
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
38
app/templates/pe/pe_view_resultats_etudiant.j2
Normal file
38
app/templates/pe/pe_view_resultats_etudiant.j2
Normal 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>
|
@ -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 %}
|
||||
|
Loading…
Reference in New Issue
Block a user