forked from ScoDoc/ScoDoc
864 lines
33 KiB
Python
864 lines
33 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
|
|
import jinja2
|
|
|
|
from app.pe.rcss import pe_rcs
|
|
from app.pe.moys import pe_sxtag
|
|
|
|
import app.pe.pe_affichage as pe_affichage
|
|
import app.pe.pe_etudiant as pe_etudiant
|
|
from app.pe.moys import (
|
|
pe_tabletags,
|
|
pe_ressemtag,
|
|
pe_sxtag,
|
|
pe_rcstag,
|
|
pe_interclasstag,
|
|
pe_moytag,
|
|
)
|
|
import app.pe.pe_rcss_jury as pe_rcss_jury
|
|
from app.scodoc.sco_utils import *
|
|
|
|
|
|
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.
|
|
|
|
Les options sont :
|
|
``
|
|
options = {
|
|
"cohorte_restreinte": False,
|
|
"moyennes_tags": True,
|
|
"moyennes_ue_res_sae": True,
|
|
"moyennes_ues_rcues": True,
|
|
"min_max_moy": False,
|
|
"synthese_individuelle_etud": False,
|
|
}
|
|
|
|
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,
|
|
formsemestre_id_base,
|
|
options={
|
|
"moyennes_tags": True,
|
|
"moyennes_ue_res_sae": True,
|
|
"moyennes_ues_rcues": True,
|
|
"min_max_moy": False,
|
|
"publipostage": False,
|
|
},
|
|
):
|
|
pe_affichage.pe_start_log()
|
|
self.diplome = diplome
|
|
"L'année du diplome"
|
|
|
|
self.formsemestre_id_base = formsemestre_id_base
|
|
"""L'identifiant du formsemestre ayant servi à lancer le jury"""
|
|
|
|
self.nom_export_zip = f"Jury_PE_{self.diplome}"
|
|
"Nom du zip où ranger les fichiers générés"
|
|
|
|
# Les options
|
|
self.options = options
|
|
"""Options de configuration (cf. pe_sem_recap)"""
|
|
|
|
pe_affichage.pe_print(
|
|
f"Données de poursuite d'étude générées le {time.strftime('%d/%m/%Y à %H:%M')}\n",
|
|
info=True,
|
|
)
|
|
|
|
pe_affichage.pe_print("Options", info=True)
|
|
for cle, val in self.options.items():
|
|
pe_affichage.pe_print(f" > {cle} -> {val}", info=True)
|
|
|
|
# Chargement des étudiants à prendre en compte dans le jury
|
|
pe_affichage.pe_print(
|
|
f"""***********************************************************"""
|
|
)
|
|
pe_affichage.pe_print(
|
|
f"""*** Recherche des étudiants diplômés 🎓 en {self.diplome}""", info=True
|
|
)
|
|
pe_affichage.pe_print(
|
|
f"""***********************************************************"""
|
|
)
|
|
|
|
# Les infos sur les étudiants
|
|
self.etudiants = pe_etudiant.EtudiantsJuryPE(self.diplome)
|
|
"""Les informations sur les étudiants du jury PE"""
|
|
self.etudiants.find_etudiants()
|
|
self.diplomes_ids = self.etudiants.diplomes_ids
|
|
|
|
self.rcss_jury = pe_rcss_jury.RCSsJuryPE(self.diplome, self.etudiants)
|
|
"""Les informations sur les regroupements de semestres"""
|
|
|
|
self.zipdata = io.BytesIO()
|
|
with ZipFile(self.zipdata, "w") as zipfile:
|
|
if not self.diplomes_ids:
|
|
pe_affichage.pe_print("*** Aucun étudiant diplômé", info=True)
|
|
else:
|
|
try:
|
|
self._gen_xls_diplomes(zipfile)
|
|
self._gen_xls_ressembuttags(zipfile)
|
|
self._gen_trajectoires()
|
|
self._gen_semXs()
|
|
self._gen_xls_sxtags(zipfile)
|
|
self._gen_rcsemxs()
|
|
self._gen_xls_rcstags(zipfile)
|
|
self._gen_xls_interclasstags(zipfile)
|
|
self._gen_xls_synthese_jury_par_tag(zipfile)
|
|
self._gen_html_synthese_par_etudiant(zipfile)
|
|
except Exception as e:
|
|
if pe_affichage.PE_DEBUG == True:
|
|
raise e
|
|
else:
|
|
pe_affichage.pe_print(str(e))
|
|
# raise e
|
|
# 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(
|
|
f"""*************************************************************************"""
|
|
)
|
|
pe_affichage.pe_print(
|
|
f"""*** Génère les ResSemBUTTag (ResSemestreBUT taggués)""", info=True
|
|
)
|
|
pe_affichage.pe_print(
|
|
f"""*************************************************************************"""
|
|
)
|
|
|
|
# Tous les formsestres des étudiants
|
|
formsemestres = get_formsemestres_etudiants(self.etudiants)
|
|
pe_affichage.pe_print(
|
|
f"1) Génère les {len(formsemestres)} ResSemBUTTag", info=True
|
|
)
|
|
|
|
self.ressembuttags = {}
|
|
for frmsem_id, formsemestre in formsemestres.items():
|
|
# Crée le semestre_tag et exécute les calculs de moyennes
|
|
ressembuttag = pe_ressemtag.ResSemBUTTag(formsemestre, options=self.options)
|
|
self.ressembuttags[frmsem_id] = ressembuttag
|
|
# Ajoute les étudiants découverts dans les ressembuttags aux données des étudiants
|
|
# nbre_etudiants_ajoutes = self.etudiants.add_etudiants(
|
|
# ressembuttag.etudids_sorted
|
|
# )
|
|
# if nbre_etudiants_ajoutes:
|
|
# pe_affichage.pe_print(
|
|
# f"--> Ajout de {nbre_etudiants_ajoutes} étudiants aux données du jury"
|
|
# )
|
|
|
|
# Intègre le bilan des semestres taggués au zip final
|
|
pe_affichage.pe_print(f"2) Bilan", info=True)
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
onglets = []
|
|
for res_sem_tag in self.ressembuttags.values():
|
|
if res_sem_tag.is_significatif():
|
|
onglet = res_sem_tag.get_repr(verbose=True)
|
|
onglet = onglet.replace("Semestre ", "S")
|
|
onglets += ["📊" + onglet]
|
|
df = res_sem_tag.to_df(options=self.options)
|
|
# 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)}", info=True
|
|
)
|
|
output.seek(0)
|
|
|
|
self.add_file_to_zip(
|
|
zipfile,
|
|
f"ResSemBUTTags_{self.diplome}.xlsx",
|
|
output.read(),
|
|
path="details",
|
|
)
|
|
|
|
def _gen_trajectoires(self):
|
|
"""Génère l'ensemble des trajectoires (RCS), qui traduisent les différents
|
|
chemins au sein des (form)semestres pour atteindre la cible d'un
|
|
RCS (par ex: 'S2' ou '3S').
|
|
"""
|
|
pe_affichage.pe_print(
|
|
"***************************************************************************"
|
|
)
|
|
pe_affichage.pe_print(
|
|
"*** Génère les trajectoires (≠tes combinaisons de semestres) des étudiants",
|
|
info=True,
|
|
)
|
|
pe_affichage.pe_print(
|
|
"***************************************************************************"
|
|
)
|
|
|
|
self.rcss_jury.cree_trajectoires()
|
|
pe_affichage.aff_trajectoires_suivies_par_etudiants(self.etudiants)
|
|
|
|
def _gen_semXs(self):
|
|
"""Génère les SemXs (trajectoires/combinaisons de semestre de même rang x)
|
|
qui traduisent les différents chemins des étudiants pour valider un semestre Sx.
|
|
"""
|
|
pe_affichage.pe_print(
|
|
"***************************************************************************"
|
|
)
|
|
pe_affichage.pe_print(
|
|
"*** Génère les SemXs (RCS de même Sx donnant lieu à validation du semestre)",
|
|
info=True,
|
|
)
|
|
pe_affichage.pe_print(
|
|
"***************************************************************************"
|
|
)
|
|
|
|
# Génère les regroupements de semestres de type Sx
|
|
|
|
self.rcss_jury.cree_semxs()
|
|
pe_affichage.aff_semXs_suivis_par_etudiants(self.etudiants)
|
|
|
|
def _gen_xls_sxtags(self, zipfile: ZipFile):
|
|
"""Génère les semestres taggués en s'appuyant sur les RCF 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(
|
|
"***************************************************************************"
|
|
)
|
|
pe_affichage.pe_print(
|
|
"*** Calcule les moyennes des SxTag (moyennes d'un RCS de type Sx)",
|
|
info=True,
|
|
)
|
|
pe_affichage.pe_print(
|
|
"***************************************************************************"
|
|
)
|
|
|
|
# Les SxTag (moyenne de Sx par UE)
|
|
pe_affichage.pe_print("1) Calcul des moyennes", info=True)
|
|
self.sxtags = {}
|
|
for rcf_id, rcf in self.rcss_jury.semXs.items():
|
|
# SxTag traduisant le RCF
|
|
sxtag_id = rcf_id
|
|
self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, rcf, self.ressembuttags)
|
|
|
|
# Intègre le bilan des semestres taggués au zip final
|
|
pe_affichage.pe_print("2) Bilan", info=True)
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
onglets = []
|
|
for sxtag in self.sxtags.values():
|
|
if sxtag.is_significatif():
|
|
onglet = sxtag.get_repr(verbose=False)
|
|
onglets += ["📊" + onglet]
|
|
df = sxtag.to_df(options=self.options)
|
|
# 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)}", info=True
|
|
)
|
|
|
|
output.seek(0)
|
|
if onglets:
|
|
self.add_file_to_zip(
|
|
zipfile,
|
|
f"semestres_taggues_{self.diplome}.xlsx",
|
|
output.read(),
|
|
path="details",
|
|
)
|
|
|
|
def _gen_rcsemxs(self):
|
|
"""Génère les regroupements cohérents de RCFs qu'ont suivi chaque étudiant"""
|
|
|
|
pe_affichage.pe_print(
|
|
"""******************************************************************************"""
|
|
)
|
|
pe_affichage.pe_print(
|
|
"""*** Génère les RCSemX (regroupements cohérents de données extraites des SemX)\n"""
|
|
"""*** amenant du S1 à un semestre final""",
|
|
info=True,
|
|
)
|
|
pe_affichage.pe_print(
|
|
"""******************************************************************************"""
|
|
)
|
|
self.rcss_jury.cree_rcsemxs(options=self.options)
|
|
if "moyennes_ues_rcues" in self.options and self.options["moyennes_ues_rcues"]:
|
|
pe_affichage.aff_rcsemxs_suivis_par_etudiants(self.etudiants)
|
|
|
|
def _gen_xls_rcstags(self, zipfile: ZipFile):
|
|
"""Génère les RCS taggués traduisant les moyennes (orientées compétences)
|
|
de regroupements de semestre de type Sx, xA ou xS.
|
|
|
|
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(
|
|
"""****************************************************"""
|
|
)
|
|
pe_affichage.pe_print(
|
|
"""*** Génère les moyennes associées aux RCSemX""", info=True
|
|
)
|
|
pe_affichage.pe_print(
|
|
"""****************************************************"""
|
|
)
|
|
|
|
pe_affichage.pe_print("1) Calcul des moyennes des RCSTag", info=True)
|
|
if not self.rcss_jury.rcsemxs:
|
|
if (
|
|
"moyennes_ues_rcues" in self.options
|
|
and not self.options["moyennes_ues_rcues"]
|
|
):
|
|
pe_affichage.pe_print(" -> Pas de RCSemX à calculer (cf. options)")
|
|
else:
|
|
pe_affichage.pe_print(
|
|
" -> Pas de RCSemX à calculer (alors qu'aucune option ne les limite) => problème"
|
|
)
|
|
self.rcsstags = {}
|
|
return
|
|
|
|
# Calcul des RCSTags sur la base des RCSemX
|
|
self.rcsstags = {}
|
|
for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items():
|
|
self.rcsstags[rcs_id] = pe_rcstag.RCSemXTag(
|
|
rcsemx, self.sxtags, self.rcss_jury.semXs_suivis
|
|
)
|
|
|
|
# Intègre le bilan des trajectoires tagguées au zip final
|
|
pe_affichage.pe_print("2) Bilan", info=True)
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
onglets = []
|
|
for rcs_tag in self.rcsstags.values():
|
|
if rcs_tag.is_significatif():
|
|
onglet = rcs_tag.get_repr(verbose=False)
|
|
onglets += ["📊" + onglet]
|
|
|
|
df = rcs_tag.to_df(options=self.options)
|
|
# 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)
|
|
pe_affichage.pe_print(
|
|
f"--> Export excel de {', '.join(onglets)}", info=True
|
|
)
|
|
output.seek(0)
|
|
|
|
if onglets:
|
|
self.add_file_to_zip(
|
|
zipfile,
|
|
f"RCRCFs_{self.diplome}.xlsx",
|
|
output.read(),
|
|
path="details",
|
|
)
|
|
|
|
def _gen_xls_interclasstags(self, zipfile: ZipFile):
|
|
"""Génère les interclassements sur la promo de diplômés
|
|
par (nom d') aggrégat
|
|
en distinguant les interclassements par accronymes d'UEs (sur les SxTag)
|
|
et ceux par compétences (sur les RCSTag).
|
|
"""
|
|
pe_affichage.pe_print(
|
|
"""******************************************************************"""
|
|
)
|
|
pe_affichage.pe_print(
|
|
"""*** Génère les interclassements sur chaque type de RCS/agrégat"""
|
|
)
|
|
pe_affichage.pe_print(
|
|
"""******************************************************************"""
|
|
)
|
|
|
|
if (
|
|
"moyennes_ues_rcues" not in self.options
|
|
or self.options["moyennes_ues_rcues"]
|
|
):
|
|
self.interclasstags = {
|
|
pe_moytag.CODE_MOY_UE: {},
|
|
pe_moytag.CODE_MOY_COMPETENCES: {},
|
|
}
|
|
else:
|
|
self.interclasstags = {pe_moytag.CODE_MOY_UE: {}}
|
|
|
|
etudiants_diplomes = self.etudiants.etudiants_diplomes
|
|
|
|
# Les interclassements par UE (toujours présents par défaut)
|
|
for Sx in pe_rcs.TOUS_LES_SEMESTRES:
|
|
interclass = pe_interclasstag.InterClassTag(
|
|
Sx,
|
|
pe_moytag.CODE_MOY_UE,
|
|
etudiants_diplomes,
|
|
self.rcss_jury.semXs,
|
|
self.sxtags,
|
|
self.rcss_jury.semXs_suivis,
|
|
)
|
|
self.interclasstags[pe_moytag.CODE_MOY_UE][Sx] = interclass
|
|
|
|
# Les interclassements par compétences
|
|
if (
|
|
"moyennes_ues_rcues" not in self.options
|
|
or self.options["moyennes_ues_rcues"]
|
|
):
|
|
for nom_rcs in pe_rcs.TOUS_LES_RCS:
|
|
interclass = pe_interclasstag.InterClassTag(
|
|
nom_rcs,
|
|
pe_moytag.CODE_MOY_COMPETENCES,
|
|
etudiants_diplomes,
|
|
self.rcss_jury.rcsemxs,
|
|
self.rcsstags,
|
|
self.rcss_jury.rcsemxs_suivis,
|
|
)
|
|
self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][
|
|
nom_rcs
|
|
] = interclass
|
|
|
|
# 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:
|
|
onglets = []
|
|
for (
|
|
type_interclass
|
|
) in self.interclasstags: # Pour les types d'interclassements prévus
|
|
interclasstag = self.interclasstags[type_interclass]
|
|
for nom_rcs, interclass in interclasstag.items():
|
|
if interclass.is_significatif():
|
|
onglet = interclass.get_repr()
|
|
onglets += ["📊" + onglet]
|
|
df = interclass.to_df(cohorte="Promo", options=self.options)
|
|
# 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)
|
|
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}", info=True)
|
|
|
|
output.seek(0)
|
|
|
|
if onglets:
|
|
self.add_file_to_zip(
|
|
zipfile,
|
|
f"InterClassTags_{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"""
|
|
pe_affichage.pe_print(
|
|
"**************************************************************************************"
|
|
)
|
|
pe_affichage.pe_print(
|
|
"*** Synthèse finale des moyennes par tag et par type de moyennes (UEs ou Compétences)",
|
|
info=True,
|
|
)
|
|
pe_affichage.pe_print(
|
|
"**************************************************************************************"
|
|
)
|
|
|
|
self.synthese = {}
|
|
pe_affichage.pe_print(" -> Synthèse des données administratives", info=True)
|
|
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 self.interclasstags:
|
|
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", info=True)
|
|
output = io.BytesIO()
|
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
|
output, engine="openpyxl"
|
|
) as writer:
|
|
onglets = []
|
|
for onglet, df in self.synthese.items():
|
|
# Conversion colonnes en multiindex
|
|
df_final = df.copy()
|
|
if (
|
|
"publipostage" not in self.options
|
|
or not self.options["publipostage"]
|
|
):
|
|
df_final = convert_colonnes_to_multiindex(df_final)
|
|
# Nom de l'onglet
|
|
if isinstance(onglet, tuple):
|
|
(repr, type_moy) = onglet
|
|
nom_onglet = onglet[0][: 31 - 7]
|
|
if type_moy == pe_moytag.CODE_MOY_COMPETENCES:
|
|
nom_onglet = nom_onglet + " (Comp)"
|
|
else:
|
|
nom_onglet = nom_onglet + " (UEs)"
|
|
else:
|
|
nom_onglet = onglet
|
|
onglets += [nom_onglet]
|
|
# écriture dans l'onglet:
|
|
df_final = df_final.replace("nan", "")
|
|
df_final.to_excel(writer, nom_onglet, index=True, header=True)
|
|
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}", info=True)
|
|
output.seek(0)
|
|
|
|
if onglets:
|
|
self.add_file_to_zip(
|
|
zipfile, f"synthese_jury_{self.diplome}.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("**************************************************")
|
|
pe_affichage.pe_print("*** Synthèse finale étudiant par étudiant", info=True)
|
|
pe_affichage.pe_print("**************************************************")
|
|
|
|
if (
|
|
"moyennes_ues_rcues" not in self.options
|
|
or self.options["moyennes_ues_rcues"]
|
|
):
|
|
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"
|
|
)
|
|
else:
|
|
pe_affichage.pe_print(" > Pas de synthèse étudiant/étudiant possible/prévu")
|
|
|
|
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, dict]):
|
|
"""La liste des tags extraites des interclassements"""
|
|
tags = []
|
|
# Pour chaque type d'interclassements
|
|
for type in interclassements:
|
|
interclassement = interclassements[type]
|
|
for aggregat in interclassement:
|
|
interclass = interclassement[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 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,
|
|
tels que fourni dans l'excel final.
|
|
|
|
Si type=UEs => tous les sxtag du tag
|
|
Si type=Compétences => tous les rcstag du tag
|
|
|
|
Args:
|
|
tag: Un des tags (a minima `but`)
|
|
type_moy: Un type de moyenne
|
|
|
|
Returns:
|
|
"""
|
|
|
|
# Les données des étudiants
|
|
etuds = [etud for etudid, etud in self.etudiants.etudiants_diplomes.items()]
|
|
df = pe_tabletags.df_administratif(etuds, aggregat="Administratif", cohorte="")
|
|
|
|
if type_moy == pe_moytag.CODE_MOY_UE:
|
|
aggregats = pe_rcs.TOUS_LES_SEMESTRES
|
|
else:
|
|
aggregats = pe_rcs.TOUS_LES_RCS
|
|
|
|
aff_aggregat = []
|
|
for aggregat in aggregats:
|
|
# Descr de l'aggrégat
|
|
descr = pe_rcs.TYPES_RCS[aggregat]["descr"]
|
|
|
|
# L'interclassement associé
|
|
interclass = self.interclasstags[type_moy][aggregat]
|
|
|
|
if interclass.is_significatif():
|
|
# Le dataframe du classement sur le groupe
|
|
df_groupe = interclass.compute_df_synthese_moyennes_tag(
|
|
tag, aggregat=aggregat, type_colonnes=False, options=self.options
|
|
)
|
|
if not df_groupe.empty:
|
|
aff_aggregat += [aggregat]
|
|
df = df.join(df_groupe)
|
|
|
|
# Le dataframe du classement sur la promo
|
|
df_promo = interclass.to_df(
|
|
administratif=False,
|
|
aggregat=aggregat,
|
|
tags_cibles=[tag],
|
|
cohorte="Promo",
|
|
options=self.options,
|
|
)
|
|
|
|
if not df_promo.empty:
|
|
aff_aggregat += [aggregat]
|
|
df = df.join(df_promo)
|
|
|
|
if aff_aggregat:
|
|
pe_affichage.pe_print(
|
|
f" -> Synthèse de 👜{tag} par {type_moy} avec {', '.join(aff_aggregat)}"
|
|
)
|
|
else:
|
|
pe_affichage.pe_print(f" -> Synthèse du tag {tag} par {type_moy} : <vide>")
|
|
|
|
return df
|
|
# Fin de l'aggrégat
|
|
|
|
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
|
|
|
|
Returns:
|
|
Un tuple nom, prenom, html
|
|
"""
|
|
etudiant = self.etudiants.identites[etudid]
|
|
nom = etudiant.nom
|
|
prenom = etudiant.prenom # initial du prénom
|
|
parcours = self.etudiants.cursus[etudid]["parcours"]
|
|
if not parcours:
|
|
parcours = "<parcours indéterminé>"
|
|
|
|
# Accès au template
|
|
environnement = jinja2.Environment(
|
|
loader=jinja2.FileSystemLoader("app/templates/")
|
|
)
|
|
template = environnement.get_template("pe/pe_view_resultats_etudiant.j2")
|
|
|
|
# Colonnes des tableaux htmls => competences
|
|
competences = []
|
|
for aggregat in pe_rcs.TOUS_LES_RCS:
|
|
# L'interclassement associé
|
|
interclass = self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][aggregat]
|
|
competences.extend(interclass.champs_sorted)
|
|
competences = sorted(set(competences))
|
|
colonnes_html = competences
|
|
|
|
tags = self._do_tags_list(self.interclasstags)
|
|
|
|
# Les données par UE
|
|
moyennes = {}
|
|
for tag in tags:
|
|
moyennes[tag] = {}
|
|
# Les données de synthèse
|
|
df = self.synthese[(tag, pe_moytag.CODE_MOY_COMPETENCES)]
|
|
for aggregat in pe_rcs.TOUS_LES_RCS:
|
|
# moyennes[tag][aggregat] = {}
|
|
descr = pe_rcs.get_descr_rcs(aggregat)
|
|
|
|
moy = {}
|
|
est_significatif = False
|
|
for comp in competences + ["Général"]:
|
|
moy[comp] = {
|
|
"note": "",
|
|
"rang_groupe": "",
|
|
"rang_promo": "",
|
|
}
|
|
colonne = pe_moytag.get_colonne_df(
|
|
aggregat, tag, comp, "Groupe", "note"
|
|
)
|
|
if colonne in df.columns:
|
|
valeur = df.loc[etudid, colonne]
|
|
if not np.isnan(valeur):
|
|
moy[comp]["note"] = round(valeur, 2)
|
|
est_significatif = True
|
|
# else:
|
|
# print(f"{colonne} manquante")
|
|
colonne = pe_moytag.get_colonne_df(
|
|
aggregat, tag, comp, "Groupe", "rang"
|
|
)
|
|
if colonne in df.columns:
|
|
valeur = df.loc[etudid, colonne]
|
|
if valeur and str(valeur) != "nan":
|
|
moy[comp]["rang_groupe"] = valeur
|
|
colonne = pe_moytag.get_colonne_df(
|
|
aggregat, tag, comp, "Promo", "rang"
|
|
)
|
|
if colonne in df.columns:
|
|
valeur = df.loc[etudid, colonne]
|
|
if valeur and str(valeur) != "nan":
|
|
moy[comp]["rang_promo"] = valeur
|
|
|
|
if est_significatif:
|
|
moyennes[tag][descr] = moy
|
|
|
|
html = template.render(
|
|
nom=nom,
|
|
prenom=prenom,
|
|
parcours=parcours,
|
|
colonnes_html=colonnes_html,
|
|
tags=tags,
|
|
moyennes=moyennes,
|
|
)
|
|
|
|
return (nom, prenom, html)
|
|
|
|
|
|
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.cursus[etudid]:
|
|
if cle.startswith("S"):
|
|
semestres = semestres | etudiants.cursus[etudid][cle]
|
|
return semestres
|
|
|
|
|
|
def convert_colonnes_to_multiindex(df):
|
|
"""Convertit les colonnes d'un df pour obtenir des colonnes
|
|
multiindex"""
|
|
df_final = df.copy()
|
|
colonnes = list(df.columns)
|
|
colonnes = [tuple(col.split("|")) for col in colonnes]
|
|
# modifie le nom du semestre par sa descr
|
|
colonnes_verbose = []
|
|
|
|
for col in colonnes:
|
|
if col[0] in pe_rcs.TYPES_RCS:
|
|
descr = pe_rcs.get_descr_rcs(col[0])
|
|
col_verbose = [descr] + list(col[1:])
|
|
col_verbose = tuple(col_verbose)
|
|
else:
|
|
col_verbose = col
|
|
colonnes_verbose.append(col_verbose)
|
|
|
|
df_final.columns = pd.MultiIndex.from_tuples(colonnes_verbose)
|
|
return df_final
|