forked from ScoDoc/ScoDoc
745 lines
29 KiB
Python
745 lines
29 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
#
|
|
##############################################################################
|
|
|
|
##############################################################################
|
|
# Module "Avis de poursuite d'étude"
|
|
# conçu et développé par Cléo Baras (IUT de Grenoble)
|
|
##############################################################################
|
|
|
|
"""
|
|
Created on Fri Sep 9 09:15:05 2016
|
|
|
|
@author: barasc
|
|
"""
|
|
|
|
# ----------------------------------------------------------
|
|
# Ensemble des fonctions et des classes
|
|
# permettant les calculs preliminaires (hors affichage)
|
|
# a l'edition d'un jury de poursuites d'etudes
|
|
# ----------------------------------------------------------
|
|
|
|
import io
|
|
import os
|
|
import time
|
|
from zipfile import ZipFile
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
from app.pe 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
|
|
import app.pe.pe_rcs as pe_rcs
|
|
from app.pe.pe_rcstag import RCSTag
|
|
from app.pe.pe_ressemtag import ResSemBUTTag
|
|
from app.pe.pe_interclasstag import RCSInterclasseTag
|
|
import app.pe.pe_comp as pe_comp
|
|
|
|
|
|
class JuryPE(object):
|
|
"""
|
|
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,
|
|
2. tous les étudiants susceptibles à ce stade (au regard de leur parcours) d'être diplomés.
|
|
|
|
Args:
|
|
diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX)
|
|
"""
|
|
|
|
def __init__(self, diplome: int):
|
|
pe_affichage.pe_start_log()
|
|
self.diplome = diplome
|
|
"L'année du diplome"
|
|
|
|
self.nom_export_zip = f"Jury_PE_{self.diplome}"
|
|
"Nom du zip où ranger les fichiers générés"
|
|
|
|
pe_affichage.pe_print(
|
|
f"Données de poursuite d'étude générées le {time.strftime('%d/%m/%Y à %H:%M')}\n"
|
|
)
|
|
# Chargement des étudiants à prendre en compte dans le jury
|
|
pe_affichage.pe_print(
|
|
f"""*** Recherche et chargement des étudiants diplômés en {
|
|
self.diplome}"""
|
|
)
|
|
self.etudiants = pe_etudiant.EtudiantsJuryPE(
|
|
self.diplome
|
|
) # Les infos sur les étudiants
|
|
self.etudiants.find_etudiants()
|
|
self.diplomes_ids = self.etudiants.diplomes_ids
|
|
|
|
self.zipdata = io.BytesIO()
|
|
with ZipFile(self.zipdata, "w") as zipfile:
|
|
if not self.diplomes_ids:
|
|
pe_affichage.pe_print("*** Aucun étudiant diplômé")
|
|
else:
|
|
self._gen_xls_diplomes(zipfile)
|
|
|
|
self._gen_xls_ressembuttags(zipfile)
|
|
self._gen_xls_sxtags(zipfile)
|
|
# self._gen_xls_rcss_tags(zipfile)
|
|
# self._gen_xls_interclassements_rcss(zipfile)
|
|
# self._gen_xls_synthese_jury_par_tag(zipfile)
|
|
# self._gen_xls_synthese_par_etudiant(zipfile)
|
|
# et le log
|
|
self._add_log_to_zip(zipfile)
|
|
|
|
# Fin !!!! Tada :)
|
|
|
|
def _gen_xls_diplomes(self, zipfile: ZipFile):
|
|
"Intègre le bilan des semestres taggués au zip"
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
if self.diplomes_ids:
|
|
onglet = "diplômés"
|
|
df_diplome = self.etudiants.df_administratif(self.diplomes_ids)
|
|
df_diplome.to_excel(writer, onglet, index=True, header=True)
|
|
if self.etudiants.abandons_ids:
|
|
onglet = "redoublants-réorientés"
|
|
df_abandon = self.etudiants.df_administratif(
|
|
self.etudiants.abandons_ids
|
|
)
|
|
df_abandon.to_excel(writer, onglet, index=True, header=True)
|
|
output.seek(0)
|
|
|
|
self.add_file_to_zip(
|
|
zipfile,
|
|
f"etudiants_{self.diplome}.xlsx",
|
|
output.read(),
|
|
path="details",
|
|
)
|
|
|
|
def _gen_xls_ressembuttags(self, zipfile: ZipFile):
|
|
"""Calcule les moyennes par tag des résultats des Semestres BUT"""
|
|
pe_affichage.pe_print(
|
|
"*** Génère les ResSemBUTTag (résultats des semestres BUT taggués)"
|
|
)
|
|
|
|
formsemestres = get_formsemestres_etudiants(self.etudiants)
|
|
pe_affichage.pe_print(
|
|
f" --> {len(formsemestres)} résultats de semestres à considérer"
|
|
)
|
|
|
|
self.ressembuttags = {}
|
|
for frmsem_id, formsemestre in formsemestres.items():
|
|
# Crée le semestre_tag et exécute les calculs de moyennes
|
|
self.ressembuttags[frmsem_id] = ResSemBUTTag(formsemestre)
|
|
|
|
# Intègre le bilan des semestres taggués au zip final
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
for res_sem_tag in self.ressembuttags.values():
|
|
onglet = res_sem_tag.get_repr(verbose=False)
|
|
df = res_sem_tag.df_moyennes_et_classements()
|
|
# écriture dans l'onglet
|
|
df.to_excel(writer, onglet, index=True, header=True)
|
|
output.seek(0)
|
|
|
|
self.add_file_to_zip(
|
|
zipfile,
|
|
f"ResSemBUTTags_{self.diplome}.xlsx",
|
|
output.read(),
|
|
path="details",
|
|
)
|
|
|
|
def _gen_rcss(self):
|
|
"""Génère les RCS (attribut `rcss_jury`), combinaisons de semestres suivis par les étudiants au sens
|
|
d'un nom de RCS (par ex: 'S2' ou '3S').
|
|
"""
|
|
pe_affichage.pe_print(
|
|
"*** Génère les RCS (différentes combinaisons de semestres) des étudiants"
|
|
)
|
|
self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome)
|
|
self.rcss_jury.cree_rcss(self.etudiants)
|
|
|
|
def _gen_xls_sxtags(self, zipfile: ZipFile):
|
|
"""Génère les semestres taggués en s'appuyant sur les RCS de type Sx (pour
|
|
identifier les redoublements impactant les semestres taggués).
|
|
"""
|
|
# Génère les moyennes des RCS de type Sx
|
|
pe_affichage.pe_print("*** Calcule les moyennes des SxTag")
|
|
|
|
# Les regroupements de Sx
|
|
self.regroupements = {}
|
|
for rang in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1):
|
|
self.regroupements[rang] = {}
|
|
for etudid in self.etudiants.etudiants_ids:
|
|
trajectoire = self.etudiants.trajectoires[etudid][f"S{rang}"]
|
|
if trajectoire:
|
|
sxtag_id = pe_etudiant.convert_trajectoire_to_sxtag_id(trajectoire)
|
|
if sxtag_id not in self.regroupements[rang]:
|
|
self.regroupements[rang][sxtag_id] = {}
|
|
else:
|
|
self.regroupements[rang][sxtag_id] |= trajectoire
|
|
|
|
# Les SxTag
|
|
self.sxtags = {}
|
|
for rang in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1):
|
|
for sxtag_id in self.regroupements[rang]:
|
|
trajectoires = self.regroupements[rang][sxtag_id]
|
|
ressemstags = {fid: self.ressembuttags[fid] for fid in trajectoires}
|
|
self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, ressemstags)
|
|
|
|
# Intègre le bilan des semestres taggués au zip final
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
for sem_tag in self.sxtags.values():
|
|
onglet = sem_tag.get_repr(verbose=False)
|
|
df = sem_tag.df_moyennes_et_classements()
|
|
# écriture dans l'onglet
|
|
df.to_excel(writer, onglet, index=True, header=True)
|
|
output.seek(0)
|
|
|
|
self.add_file_to_zip(
|
|
zipfile,
|
|
f"semestres_taggues_{self.diplome}.xlsx",
|
|
output.read(),
|
|
path="details",
|
|
)
|
|
|
|
def _gen_xls_rcss_tags(self, zipfile: ZipFile):
|
|
"""Génère les RCS taggués (autres que ceux de type Sx), etc...
|
|
en calculant les moyennes et les classements par tag pour chaque RCS.
|
|
|
|
Stocke le résultat dans self.rccs_tag, un dictionnaire de
|
|
la forme ``{nom_aggregat: {fid_terminal: RCSTag(fid_terminal)} }``
|
|
|
|
Pour rappel : Chaque RCS est identifié par un nom d'aggrégat et par un formsemestre terminal.
|
|
|
|
Par exemple :
|
|
|
|
* combinaisons '3S' : S1+S2+S3 en prenant en compte tous les S3 qu'ont fréquenté les
|
|
étudiants du jury PE. Ces S3 marquent les formsemestre terminal de chaque combinaison.
|
|
|
|
Args:
|
|
etudiants: Les données des étudiants
|
|
semestres_tag: Les semestres tag (pour lesquels des moyennes par tag ont été calculés)
|
|
"""
|
|
|
|
# Génère les moyennes des RCS de type Sx
|
|
pe_affichage.pe_print("*** Calcule les moyennes des RCS")
|
|
|
|
self.rcss_tags = {}
|
|
for rcs_id, rcs in self.rcss_jury.rcss.items():
|
|
# nom = rcs.get_repr()
|
|
self.rcss_tags[rcs_id] = RCSTag(rcs, self.res_sems_tags)
|
|
|
|
# Intègre le bilan des trajectoires tagguées au zip final
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
for rcs_tag in self.rcss_tags.values():
|
|
onglet = rcs_tag.get_repr(mode="short")
|
|
df = rcs_tag.df_moyennes_et_classements()
|
|
# écriture dans l'onglet
|
|
df.to_excel(writer, onglet, index=True, header=True)
|
|
output.seek(0)
|
|
|
|
self.add_file_to_zip(
|
|
zipfile,
|
|
f"RCS_taggues_{self.diplome}.xlsx",
|
|
output.read(),
|
|
path="details",
|
|
)
|
|
|
|
def _gen_xls_interclassements_rcss(self, zipfile: ZipFile):
|
|
"""Intègre le bilan des RCS (interclassé par promo) au zip"""
|
|
# Génère les interclassements (par promo et) par (nom d') aggrégat
|
|
pe_affichage.pe_print("*** Génère les interclassements par aggrégat")
|
|
self.interclassements_taggues = compute_interclassements(
|
|
self.etudiants, self.rcss_jury, self.rcss_tags
|
|
)
|
|
|
|
# Intègre le bilan des aggrégats (interclassé par promo) au zip final
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
for interclass_tag in self.interclassements_taggues.values():
|
|
if interclass_tag.significatif: # Avec des notes
|
|
onglet = interclass_tag.get_repr()
|
|
df = interclass_tag.df_moyennes_et_classements()
|
|
# écriture dans l'onglet
|
|
df.to_excel(writer, onglet, index=True, header=True)
|
|
output.seek(0)
|
|
|
|
self.add_file_to_zip(
|
|
zipfile,
|
|
f"interclassements_taggues_{self.diplome}.xlsx",
|
|
output.read(),
|
|
path="details",
|
|
)
|
|
|
|
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()
|
|
|
|
# Export des données => mode 1 seule feuille -> supprimé
|
|
pe_affichage.pe_print("*** Export du jury de synthese par tags")
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
for onglet, df in self.synthese.items():
|
|
# écriture dans l'onglet:
|
|
df.to_excel(writer, onglet, index=True, header=True)
|
|
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:
|
|
for onglet, df in synthese.items():
|
|
# écriture dans l'onglet:
|
|
df.to_excel(writer, onglet, index=True, header=True)
|
|
output.seek(0)
|
|
|
|
self.add_file_to_zip(
|
|
zipfile, f"synthese_jury_{self.diplome}_par_etudiant.xlsx", output.read()
|
|
)
|
|
|
|
def _add_log_to_zip(self, zipfile):
|
|
"""Add a text file with the log messages"""
|
|
log_data = pe_affichage.pe_get_log()
|
|
self.add_file_to_zip(zipfile, "pe_log.txt", log_data)
|
|
|
|
def add_file_to_zip(self, zipfile: ZipFile, filename: str, data, path=""):
|
|
"""Add a file to given zip
|
|
All files under NOM_EXPORT_ZIP/
|
|
path may specify a subdirectory
|
|
|
|
Args:
|
|
zipfile: ZipFile
|
|
filename: Le nom du fichier à intégrer au zip
|
|
data: Les données du fichier
|
|
path: Un dossier dans l'arborescence du zip
|
|
"""
|
|
path_in_zip = os.path.join(path, filename) # self.nom_export_zip,
|
|
zipfile.writestr(path_in_zip, data)
|
|
|
|
def get_zipped_data(self) -> io.BytesIO | None:
|
|
"""returns file-like data with a zip of all generated (CSV) files.
|
|
Warning: reset stream to the begining.
|
|
"""
|
|
self.zipdata.seek(0)
|
|
return self.zipdata
|
|
|
|
def do_tags_list(self, interclassements: dict[str, RCSInterclasseTag]):
|
|
"""La liste des tags extraites des interclassements"""
|
|
tags = []
|
|
for aggregat in interclassements:
|
|
interclass = interclassements[aggregat]
|
|
if interclass.tags_sorted:
|
|
tags.extend(interclass.tags_sorted)
|
|
tags = sorted(set(tags))
|
|
return tags
|
|
|
|
# **************************************************************************************************************** #
|
|
# 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"""
|
|
|
|
pe_affichage.pe_print("*** Synthèse finale des moyennes par tag***")
|
|
|
|
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.interclassements_taggues)
|
|
for tag in tags:
|
|
pe_affichage.pe_print(f" -> Synthèse du tag {tag}")
|
|
synthese[tag] = self.df_tag(tag)
|
|
return synthese
|
|
|
|
def df_tag(self, tag):
|
|
"""Génère le DataFrame synthétisant les moyennes/classements (groupe,
|
|
interclassement promo) pour tous les aggrégats prévus,
|
|
tels que fourni dans l'excel final.
|
|
|
|
Args:
|
|
tag: Un des tags (a minima `but`)
|
|
|
|
Returns:
|
|
"""
|
|
|
|
etudids = list(self.diplomes_ids)
|
|
|
|
# Les données des étudiants
|
|
donnees_etudiants = {}
|
|
for etudid in etudids:
|
|
etudiant = self.etudiants.identites[etudid]
|
|
donnees_etudiants[etudid] = {
|
|
("Identité", "", "Civilite"): etudiant.civilite_str,
|
|
("Identité", "", "Nom"): etudiant.nom,
|
|
("Identité", "", "Prenom"): etudiant.prenom,
|
|
}
|
|
df_synthese = pd.DataFrame.from_dict(donnees_etudiants, orient="index")
|
|
|
|
# Ajout des aggrégats
|
|
for aggregat in pe_rcs.TOUS_LES_RCS:
|
|
descr = pe_rcs.TYPES_RCS[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.rcss_jury.suivi[etudid][aggregat]
|
|
if trajectoire:
|
|
tid = trajectoire.sxtag_id
|
|
trajectoire_tagguee = self.rcss_tags[tid]
|
|
if (
|
|
tag in trajectoire_tagguee.moyennes_tags
|
|
and trajectoire_tagguee not in trajectoires_tagguees
|
|
):
|
|
trajectoires_tagguees.append(trajectoire_tagguee)
|
|
|
|
# Combien de notes vont être injectées ?
|
|
nbre_notes_injectees = 0
|
|
for traj in trajectoires_tagguees:
|
|
moy_traj = traj.moyennes_tags[tag]
|
|
inscrits_traj = moy_traj.inscrits_ids
|
|
etudids_communs = set(etudids) & set(inscrits_traj)
|
|
nbre_notes_injectees += len(etudids_communs)
|
|
|
|
# Si l'aggrégat est significatif (aka il y a des notes)
|
|
if nbre_notes_injectees > 0:
|
|
# Ajout des données classements & statistiques
|
|
nom_stat_promo = f"{NOM_STAT_PROMO} {self.diplome}"
|
|
donnees = pd.DataFrame(
|
|
index=etudids,
|
|
columns=[
|
|
[descr] * (1 + 4 * 2),
|
|
[""] + [NOM_STAT_GROUPE] * 4 + [nom_stat_promo] * 4,
|
|
["note"] + ["class.", "min", "moy", "max"] * 2,
|
|
],
|
|
)
|
|
|
|
for traj in trajectoires_tagguees:
|
|
# Les données des trajectoires_tagguees
|
|
moy_traj = traj.moyennes_tags[tag]
|
|
|
|
# Les étudiants communs entre tableur de synthèse et trajectoires
|
|
inscrits_traj = moy_traj.inscrits_ids
|
|
etudids_communs = list(set(etudids) & set(inscrits_traj))
|
|
|
|
# Les notes
|
|
champ = (descr, "", "note")
|
|
notes_traj = moy_traj.get_notes()
|
|
donnees.loc[etudids_communs, champ] = notes_traj.loc[
|
|
etudids_communs
|
|
]
|
|
|
|
# Les rangs
|
|
champ = (descr, NOM_STAT_GROUPE, "class.")
|
|
rangs = moy_traj.get_rangs_inscrits()
|
|
donnees.loc[etudids_communs, champ] = rangs.loc[etudids_communs]
|
|
|
|
# Les mins
|
|
champ = (descr, NOM_STAT_GROUPE, "min")
|
|
mins = moy_traj.get_min()
|
|
donnees.loc[etudids_communs, champ] = mins.loc[etudids_communs]
|
|
|
|
# Les max
|
|
champ = (descr, NOM_STAT_GROUPE, "max")
|
|
maxs = moy_traj.get_max()
|
|
donnees.loc[etudids_communs, champ] = maxs.loc[etudids_communs]
|
|
|
|
# Les moys
|
|
champ = (descr, NOM_STAT_GROUPE, "moy")
|
|
moys = moy_traj.get_moy()
|
|
donnees.loc[etudids_communs, champ] = moys.loc[etudids_communs]
|
|
|
|
# Ajoute les données d'interclassement
|
|
interclass = self.interclassements_taggues[aggregat]
|
|
moy_interclass = interclass.moyennes_tags[tag]
|
|
|
|
# Les étudiants communs entre tableur de synthèse et l'interclassement
|
|
inscrits_interclass = moy_interclass.inscrits_ids
|
|
etudids_communs = list(set(etudids) & set(inscrits_interclass))
|
|
|
|
# Les classements d'interclassement
|
|
champ = (descr, nom_stat_promo, "class.")
|
|
rangs = moy_interclass.get_rangs_inscrits()
|
|
donnees.loc[etudids_communs, champ] = rangs.loc[etudids_communs]
|
|
|
|
# Les mins
|
|
champ = (descr, nom_stat_promo, "min")
|
|
mins = moy_interclass.get_min()
|
|
donnees.loc[etudids_communs, champ] = mins.loc[etudids_communs]
|
|
|
|
# Les max
|
|
champ = (descr, nom_stat_promo, "max")
|
|
maxs = moy_interclass.get_max()
|
|
donnees.loc[etudids_communs, champ] = maxs.loc[etudids_communs]
|
|
|
|
# Les moys
|
|
champ = (descr, nom_stat_promo, "moy")
|
|
moys = moy_interclass.get_moy()
|
|
donnees.loc[etudids_communs, champ] = moys.loc[etudids_communs]
|
|
|
|
df_synthese = df_synthese.join(donnees)
|
|
# Fin de l'aggrégat
|
|
|
|
# Tri par nom/prénom
|
|
df_synthese.sort_values(
|
|
by=[("Identité", "", "Nom"), ("Identité", "", "Prenom")], inplace=True
|
|
)
|
|
return df_synthese
|
|
|
|
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***")
|
|
|
|
synthese = {}
|
|
pe_affichage.pe_print(" -> Synthèse des données administratives")
|
|
synthese["administratif"] = self.etudiants.df_administratif(self.diplomes_ids)
|
|
|
|
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.interclassements_taggues)
|
|
|
|
donnees = {}
|
|
|
|
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.suivi[etudid][aggregat]
|
|
if trajectoire:
|
|
trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id]
|
|
if tag in trajectoire_tagguee.moyennes_tags:
|
|
# 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,
|
|
)
|
|
|
|
# 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
|
|
|
|
|
|
def get_formsemestres_etudiants(etudiants: pe_etudiant.EtudiantsJuryPE) -> dict:
|
|
"""Ayant connaissance des étudiants dont il faut calculer les moyennes pour
|
|
le jury PE (attribut `self.etudiant_ids) et de leurs trajectoires (semestres
|
|
parcourus), renvoie un dictionnaire ``{fid: FormSemestre(fid)}``
|
|
contenant l'ensemble des formsemestres de leurs cursus, dont il faudra calculer
|
|
la moyenne.
|
|
|
|
Args:
|
|
etudiants: Les étudiants du jury PE
|
|
|
|
Returns:
|
|
Un dictionnaire de la forme `{fid: FormSemestre(fid)}`
|
|
"""
|
|
semestres = {}
|
|
for etudid in etudiants.etudiants_ids:
|
|
for cle in etudiants.trajectoires[etudid]:
|
|
if cle.startswith("S"):
|
|
semestres = semestres | etudiants.trajectoires[etudid][cle]
|
|
return semestres
|
|
|
|
|
|
def compute_interclassements(
|
|
etudiants: pe_etudiant.EtudiantsJuryPE,
|
|
trajectoires_jury_pe: pe_rcs.RCSsJuryPE,
|
|
trajectoires_tagguees: dict[tuple, pe_rcs.RCS],
|
|
):
|
|
"""Interclasse les étudiants, (nom d') aggrégat par aggrégat,
|
|
pour fournir un classement sur la promo. Le classement est établi au regard du nombre
|
|
d'étudiants ayant participé au même aggrégat.
|
|
"""
|
|
aggregats_interclasses_taggues = {}
|
|
for nom_aggregat in pe_rcs.TOUS_LES_RCS:
|
|
pe_affichage.pe_print(f" --> Interclassement {nom_aggregat}")
|
|
interclass = RCSInterclasseTag(
|
|
nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees
|
|
)
|
|
aggregats_interclasses_taggues[nom_aggregat] = interclass
|
|
return aggregats_interclasses_taggues
|
|
|
|
|
|
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: RCSTag,
|
|
interclassement_taggue: RCSInterclasseTag,
|
|
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
|