Compare commits
7 Commits
5ea65433be
...
b1602f0cf3
Author | SHA1 | Date | |
---|---|---|---|
b1602f0cf3 | |||
ba28d5f3c8 | |||
b9b9a172c7 | |||
c2a66b607f | |||
802e8f4648 | |||
cf7d7d2db8 | |||
bcb801662a |
61
app/forms/pe/pe_sem_recap.py
Normal file
61
app/forms/pe/pe_sem_recap.py
Normal file
@ -0,0 +1,61 @@
|
||||
##############################################################################
|
||||
#
|
||||
# ScoDoc
|
||||
#
|
||||
# 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
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
Formulaire options génération table poursuite études (PE)
|
||||
"""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import BooleanField, HiddenField, SubmitField
|
||||
|
||||
|
||||
class ParametrageClasseurPE(FlaskForm):
|
||||
"Formulaire paramétrage génération classeur PE"
|
||||
# cohorte_restreinte = BooleanField(
|
||||
# "Restreindre aux étudiants inscrits dans le semestre (sans interclassement de promotion) (à venir)"
|
||||
# )
|
||||
moyennes_tags = BooleanField(
|
||||
"Générer les moyennes sur les tags de modules personnalisés (cf. programme de formation)",
|
||||
default=True,
|
||||
render_kw={"checked": ""},
|
||||
)
|
||||
moyennes_ue_res_sae = BooleanField(
|
||||
"Générer les moyennes des ressources et des SAEs",
|
||||
default=True,
|
||||
render_kw={"checked": ""},
|
||||
)
|
||||
moyennes_ues_rcues = BooleanField(
|
||||
"Générer les moyennes par RCUEs (compétences) et leurs synthèses HTML étudiant par étudiant",
|
||||
default=True,
|
||||
render_kw={"checked": ""},
|
||||
)
|
||||
|
||||
min_max_moy = BooleanField("Afficher les colonnes min/max/moy")
|
||||
|
||||
# synthese_individuelle_etud = BooleanField(
|
||||
# "Générer (suppose les RCUES)"
|
||||
# )
|
||||
|
||||
submit = SubmitField("Générer les classeurs poursuites d'études")
|
||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
@ -280,7 +280,7 @@ class InterClassTag(pe_tabletags.TableTag):
|
||||
return None
|
||||
|
||||
def compute_df_synthese_moyennes_tag(
|
||||
self, tag, aggregat=None, type_colonnes=False
|
||||
self, tag, aggregat=None, type_colonnes=False, options={"min_max_moy": True}
|
||||
) -> pd.DataFrame:
|
||||
"""Construit le dataframe retraçant pour les données des moyennes
|
||||
pour affichage dans la synthèse du jury PE. (cf. to_df())
|
||||
@ -322,8 +322,7 @@ class InterClassTag(pe_tabletags.TableTag):
|
||||
if tag in rcstag.moyennes_tags:
|
||||
moytag: pd.DataFrame = rcstag.moyennes_tags[tag]
|
||||
df_moytag = moytag.to_df(
|
||||
aggregat=aggregat,
|
||||
cohorte="Groupe",
|
||||
aggregat=aggregat, cohorte="Groupe", options=options
|
||||
)
|
||||
|
||||
# Modif les colonnes au regard du 1er df_moytag significatif lu
|
||||
|
@ -18,7 +18,12 @@ class Moyenne:
|
||||
]
|
||||
"""Colonnes du df"""
|
||||
|
||||
COLONNES_SYNTHESE = ["note", "rang", "min", "max", "moy"]
|
||||
@classmethod
|
||||
def get_colonnes_synthese(cls, with_min_max_moy):
|
||||
if with_min_max_moy:
|
||||
return ["note", "rang", "min", "max", "moy"]
|
||||
else:
|
||||
return ["note", "rang"]
|
||||
|
||||
def __init__(self, notes: pd.Series):
|
||||
"""Classe centralisant la synthèse des moyennes/classements d'une série
|
||||
@ -97,9 +102,12 @@ class Moyenne:
|
||||
|
||||
return df
|
||||
|
||||
def get_df_synthese(self):
|
||||
def get_df_synthese(self, with_min_max_moy=None):
|
||||
"""Renvoie le df de synthese limité aux colonnes de synthese"""
|
||||
df = self.df[self.COLONNES_SYNTHESE].copy()
|
||||
colonnes_synthese = Moyenne.get_colonnes_synthese(
|
||||
with_min_max_moy=with_min_max_moy
|
||||
)
|
||||
df = self.df[colonnes_synthese].copy()
|
||||
df["rang"] = df["rang"].replace("nan", "")
|
||||
return df
|
||||
|
||||
|
@ -107,15 +107,18 @@ class MoyennesTag:
|
||||
|
||||
return moy_gen_tag
|
||||
|
||||
def to_df(self, aggregat=None, cohorte=None) -> pd.DataFrame:
|
||||
def to_df(
|
||||
self, aggregat=None, cohorte=None, options={"min_max_moy": True}
|
||||
) -> pd.DataFrame:
|
||||
"""Renvoie le df synthétisant l'ensemble des données
|
||||
connues
|
||||
Adapte les intitulés des colonnes aux données fournies
|
||||
(nom d'aggrégat, type de cohorte).
|
||||
|
||||
`pole` détermine les modules à prendre en compte dans la moyenne (None=tous,
|
||||
RESSOURCES ou SAES)
|
||||
"""
|
||||
if "min_max_moy" not in options or options["min_max_moy"]:
|
||||
with_min_max_moy = True
|
||||
else:
|
||||
with_min_max_moy = False
|
||||
|
||||
etudids_sorted = sorted(self.etudids)
|
||||
|
||||
@ -124,20 +127,27 @@ class MoyennesTag:
|
||||
# Ajout des notes pour tous les champs
|
||||
champs = list(self.champs)
|
||||
for champ in champs:
|
||||
df_champ = self.moyennes_gen[champ].get_df_synthese() # le dataframe
|
||||
df_champ = self.moyennes_gen[champ].get_df_synthese(
|
||||
with_min_max_moy=with_min_max_moy
|
||||
) # le dataframe
|
||||
# Renomme les colonnes
|
||||
|
||||
cols = [
|
||||
get_colonne_df(aggregat, self.tag, champ, cohorte, critere)
|
||||
for critere in pe_moy.Moyenne.COLONNES_SYNTHESE
|
||||
for critere in pe_moy.Moyenne.get_colonnes_synthese(
|
||||
with_min_max_moy=with_min_max_moy
|
||||
)
|
||||
]
|
||||
df_champ.columns = cols
|
||||
df = df.join(df_champ)
|
||||
|
||||
# Ajoute la moy générale
|
||||
df_moy_gen = self.moyenne_gen.get_df_synthese()
|
||||
df_moy_gen = self.moyenne_gen.get_df_synthese(with_min_max_moy=with_min_max_moy)
|
||||
cols = [
|
||||
get_colonne_df(aggregat, self.tag, CHAMP_GENERAL, cohorte, critere)
|
||||
for critere in pe_moy.Moyenne.COLONNES_SYNTHESE
|
||||
for critere in pe_moy.Moyenne.get_colonnes_synthese(
|
||||
with_min_max_moy=with_min_max_moy
|
||||
)
|
||||
]
|
||||
df_moy_gen.columns = cols
|
||||
df = df.join(df_moy_gen)
|
||||
|
@ -56,10 +56,15 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
Il s'appuie principalement sur un ResultatsSemestreBUT.
|
||||
"""
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
def __init__(
|
||||
self,
|
||||
formsemestre: FormSemestre,
|
||||
options={"moyennes_tags": True, "moyennes_ue_res_sae": False},
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
formsemestre: le ``FormSemestre`` sur lequel il se base
|
||||
options: Un dictionnaire d'options
|
||||
"""
|
||||
ResultatsSemestreBUT.__init__(self, formsemestre)
|
||||
pe_tabletags.TableTag.__init__(self)
|
||||
@ -118,7 +123,11 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
pe_affichage.pe_print(f"--> UEs/Compétences : {aff}")
|
||||
|
||||
# Les tags personnalisés et auto:
|
||||
tags_dict = self._get_tags_dict()
|
||||
if "moyennes_tags" in options:
|
||||
tags_dict = self._get_tags_dict(avec_moyennes_tags=options["moyennes_tags"])
|
||||
else:
|
||||
tags_dict = self._get_tags_dict()
|
||||
|
||||
pe_affichage.pe_print(
|
||||
f"""--> {pe_affichage.aff_tags_par_categories(tags_dict)}"""
|
||||
)
|
||||
@ -165,22 +174,26 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
)
|
||||
|
||||
# Ajoute la moyenne générale par ressources
|
||||
moy_res_gen = self.compute_moy_ues_tag(info_tag=None, pole=ModuleType.RESSOURCE)
|
||||
self.moyennes_tags["ressources"] = pe_moytag.MoyennesTag(
|
||||
"ressources",
|
||||
pe_moytag.CODE_MOY_UE,
|
||||
moy_res_gen,
|
||||
self.matrice_coeffs_moy_gen,
|
||||
)
|
||||
if "moyennes_ue_res_sae" in options and options["moyennes_ue_res_sae"]:
|
||||
moy_res_gen = self.compute_moy_ues_tag(
|
||||
info_tag=None, pole=ModuleType.RESSOURCE
|
||||
)
|
||||
self.moyennes_tags["ressources"] = pe_moytag.MoyennesTag(
|
||||
"ressources",
|
||||
pe_moytag.CODE_MOY_UE,
|
||||
moy_res_gen,
|
||||
self.matrice_coeffs_moy_gen,
|
||||
)
|
||||
|
||||
# Ajoute la moyenne générale par saes
|
||||
moy_saes_gen = self.compute_moy_ues_tag(info_tag=None, pole=ModuleType.SAE)
|
||||
self.moyennes_tags["saes"] = pe_moytag.MoyennesTag(
|
||||
"saes",
|
||||
pe_moytag.CODE_MOY_UE,
|
||||
moy_saes_gen,
|
||||
self.matrice_coeffs_moy_gen,
|
||||
)
|
||||
if "moyennes_ue_res_sae" in options and options["moyennes_ue_res_sae"]:
|
||||
moy_saes_gen = self.compute_moy_ues_tag(info_tag=None, pole=ModuleType.SAE)
|
||||
self.moyennes_tags["saes"] = pe_moytag.MoyennesTag(
|
||||
"saes",
|
||||
pe_moytag.CODE_MOY_UE,
|
||||
moy_saes_gen,
|
||||
self.matrice_coeffs_moy_gen,
|
||||
)
|
||||
|
||||
# Tous les tags
|
||||
self.tags_sorted = self.get_all_significant_tags()
|
||||
@ -347,7 +360,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
|
||||
return df_ues
|
||||
|
||||
def _get_tags_dict(self):
|
||||
def _get_tags_dict(self, avec_moyennes_tags=True):
|
||||
"""Renvoie les tags personnalisés (déduits des modules du semestre)
|
||||
et les tags automatiques ('but'), et toutes leurs informations,
|
||||
dans un dictionnaire de la forme :
|
||||
@ -359,10 +372,12 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
Le dictionnaire structuré des tags ("personnalises" vs. "auto")
|
||||
"""
|
||||
dict_tags = {"personnalises": dict(), "auto": dict()}
|
||||
# Les tags perso
|
||||
dict_tags["personnalises"] = get_synthese_tags_personnalises_semestre(
|
||||
self.formsemestre
|
||||
)
|
||||
|
||||
if avec_moyennes_tags:
|
||||
# Les tags perso (seulement si l'option d'utiliser les tags perso est choisie)
|
||||
dict_tags["personnalises"] = get_synthese_tags_personnalises_semestre(
|
||||
self.formsemestre
|
||||
)
|
||||
|
||||
# Les tags automatiques
|
||||
# Déduit des compétences
|
||||
|
@ -89,6 +89,7 @@ class TableTag(object):
|
||||
aggregat=None,
|
||||
tags_cibles=None,
|
||||
cohorte=None,
|
||||
options={"min_max_moy": True},
|
||||
) -> pd.DataFrame:
|
||||
"""Renvoie un dataframe listant toutes les données
|
||||
des moyennes/classements/nb_inscrits/min/max/moy
|
||||
@ -126,7 +127,7 @@ class TableTag(object):
|
||||
for tag in tags_cibles:
|
||||
if tag in self.moyennes_tags:
|
||||
moy_tag_df = self.moyennes_tags[tag].to_df(
|
||||
aggregat=aggregat, cohorte=cohorte
|
||||
aggregat=aggregat, cohorte=cohorte, options=options
|
||||
)
|
||||
df = df.join(moy_tag_df)
|
||||
|
||||
|
@ -127,10 +127,15 @@ def aff_tags_par_categories(dict_tags):
|
||||
"""
|
||||
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
|
||||
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
|
||||
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
|
||||
aff_tags_perso = ", ".join([f"👜{nom}" for nom in noms_tags_perso])
|
||||
if noms_tags_perso:
|
||||
aff_tags_perso = ", ".join([f"👜{nom}" for nom in noms_tags_perso])
|
||||
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
|
||||
return f"Tags du programme de formation : {aff_tags_perso} + Automatiques : {aff_tags_auto}"
|
||||
else:
|
||||
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
|
||||
return f"Tags automatiques {aff_tags_auto} (aucun tag personnalisé)"
|
||||
|
||||
# Affichage
|
||||
return f"Tags du programme de formation : {aff_tags_perso} + Automatiques : {aff_tags_auto}"
|
||||
|
||||
|
||||
def aff_trajectoires_suivies_par_etudiants(etudiants):
|
||||
@ -202,3 +207,28 @@ def repr_comp_et_ues(acronymes_ues_to_competences):
|
||||
liste += ["📍" + acro]
|
||||
aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"]
|
||||
return "\n".join(aff_comp)
|
||||
|
||||
|
||||
def aff_rcsemxs_suivis_par_etudiants(etudiants):
|
||||
"""Affiche les RCSemX (regroupement de SemX)
|
||||
amenant un étudiant du S1 à un Sx"""
|
||||
etudiants_ids = etudiants.etudiants_ids
|
||||
jeunes = list(enumerate(etudiants_ids))
|
||||
|
||||
for no_etud, etudid in jeunes:
|
||||
etat = "⛔" if etudid in etudiants.abandons_ids else "✅"
|
||||
pe_print(f"-> {etat} {etudiants.identites[etudid].nomprenom} :")
|
||||
for nom_rcs, rcs in etudiants.rcsemXs[etudid].items():
|
||||
if rcs:
|
||||
pe_print(f" > RCSemX ⏯️{nom_rcs}: {rcs.get_repr()}")
|
||||
|
||||
vides = []
|
||||
for nom_rcs in pe_rcs.TOUS_LES_RCS:
|
||||
les_rcssemX_suivis = []
|
||||
for no_etud, etudid in jeunes:
|
||||
if etudiants.rcsemXs[etudid][nom_rcs]:
|
||||
les_rcssemX_suivis.append(etudiants.rcsemXs[etudid][nom_rcs])
|
||||
if not les_rcssemX_suivis:
|
||||
vides += [nom_rcs]
|
||||
vides = sorted(list(set(vides)))
|
||||
pe_print(f"⚠️ RCSemX vides : {', '.join(vides)}")
|
||||
|
@ -69,6 +69,9 @@ class EtudiantsJuryPE:
|
||||
self.semXs: dict[int:dict] = {}
|
||||
"""Les semXs (RCS de type Sx) suivis par chaque étudiant"""
|
||||
|
||||
self.rcsemXs: dict[int:dict] = {}
|
||||
"""Les RC de SemXs (RCS de type Sx, xA, xS) suivis par chaque étudiant"""
|
||||
|
||||
self.etudiants_diplomes = {}
|
||||
"""Les identités des étudiants à considérer au jury (ceux qui seront effectivement
|
||||
diplômés)"""
|
||||
@ -274,6 +277,7 @@ class EtudiantsJuryPE:
|
||||
# Initialise ses trajectoires/SemX/RCSemX
|
||||
self.trajectoires[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS}
|
||||
self.semXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_SEMESTRES}
|
||||
self.rcsemXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS}
|
||||
|
||||
def structure_cursus_etudiant(self, etudid: int):
|
||||
"""Structure les informations sur les semestres suivis par un
|
||||
|
@ -75,22 +75,45 @@ class JuryPE(object):
|
||||
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):
|
||||
def __init__(self, diplome: int, formsemestre_id_base, options=None):
|
||||
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"""***********************************************************"""
|
||||
@ -180,7 +203,7 @@ class JuryPE(object):
|
||||
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)
|
||||
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(
|
||||
@ -326,8 +349,9 @@ class JuryPE(object):
|
||||
pe_affichage.pe_print(
|
||||
"""******************************************************************************"""
|
||||
)
|
||||
self.rcss_jury.cree_rcsemxs(self.etudiants)
|
||||
self.rcss_jury._aff_rcsemxs_suivis(self.etudiants)
|
||||
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)
|
||||
@ -360,6 +384,20 @@ class JuryPE(object):
|
||||
)
|
||||
|
||||
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(
|
||||
@ -407,20 +445,26 @@ class JuryPE(object):
|
||||
"""******************************************************************"""
|
||||
)
|
||||
pe_affichage.pe_print(
|
||||
"""*** Génère les interclassements sur chaque type de RCS/agrgégat"""
|
||||
"""*** Génère les interclassements sur chaque type de RCS/agrégat"""
|
||||
)
|
||||
pe_affichage.pe_print(
|
||||
"""******************************************************************"""
|
||||
)
|
||||
|
||||
self.interclasstags = {
|
||||
pe_moytag.CODE_MOY_UE: {},
|
||||
pe_moytag.CODE_MOY_COMPETENCES: {},
|
||||
}
|
||||
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
|
||||
# Les interclassements par UE (toujours présents par défaut)
|
||||
for Sx in pe_rcs.TOUS_LES_SEMESTRES:
|
||||
interclass = pe_interclasstag.InterClassTag(
|
||||
Sx,
|
||||
@ -433,16 +477,22 @@ class JuryPE(object):
|
||||
self.interclasstags[pe_moytag.CODE_MOY_UE][Sx] = interclass
|
||||
|
||||
# Les interclassements par compétences
|
||||
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
|
||||
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()
|
||||
@ -450,10 +500,9 @@ class JuryPE(object):
|
||||
output, engine="openpyxl"
|
||||
) as writer:
|
||||
onglets = []
|
||||
for type_interclass in [
|
||||
pe_moytag.CODE_MOY_UE,
|
||||
pe_moytag.CODE_MOY_COMPETENCES,
|
||||
]:
|
||||
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():
|
||||
@ -498,7 +547,7 @@ class JuryPE(object):
|
||||
|
||||
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]:
|
||||
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é
|
||||
@ -529,9 +578,10 @@ class JuryPE(object):
|
||||
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}", info=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile, f"synthese_jury_{self.diplome}_par_tag.xlsx", output.read()
|
||||
)
|
||||
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"""
|
||||
@ -540,12 +590,18 @@ class JuryPE(object):
|
||||
pe_affichage.pe_print("*** Synthèse finale étudiant par étudiant", info=True)
|
||||
pe_affichage.pe_print("**************************************************")
|
||||
|
||||
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"
|
||||
)
|
||||
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"""
|
||||
@ -626,7 +682,7 @@ class JuryPE(object):
|
||||
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
|
||||
tag, aggregat=aggregat, type_colonnes=False, options=self.options
|
||||
)
|
||||
if not df_groupe.empty:
|
||||
aff_aggregat += [aggregat]
|
||||
@ -638,6 +694,7 @@ class JuryPE(object):
|
||||
aggregat=aggregat,
|
||||
tags_cibles=[tag],
|
||||
cohorte="Promo",
|
||||
options=self.options,
|
||||
)
|
||||
|
||||
if not df_promo.empty:
|
||||
|
@ -117,7 +117,7 @@ class RCSsJuryPE:
|
||||
self.semXs_suivis[etudid][agregat] = semX
|
||||
self.etudiants.semXs[etudid][agregat] = semX
|
||||
|
||||
def cree_rcsemxs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
|
||||
def cree_rcsemxs(self, options={"moyennes_ues_rcues": True}):
|
||||
"""Créé tous les RCSemXs, au regard du cursus des étudiants
|
||||
analysés (trajectoires traduisant son parcours dans les
|
||||
différents semestres) + les mémorise dans les données de l'étudiant
|
||||
@ -125,6 +125,11 @@ class RCSsJuryPE:
|
||||
self.rcsemxs_suivis = {}
|
||||
self.rcsemxs = {}
|
||||
|
||||
if "moyennes_ues_rcues" in options and options["moyennes_ues_rcues"] == False:
|
||||
# Pas de RCSemX généré
|
||||
pe_affichage.pe_print("⚠️ Pas de RCSemX générés")
|
||||
return
|
||||
|
||||
# Pour tous les étudiants du jury
|
||||
pas_de_semestres = []
|
||||
for etudid in self.trajectoires_suivies:
|
||||
@ -132,20 +137,11 @@ class RCSsJuryPE:
|
||||
nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
|
||||
}
|
||||
|
||||
# Recopie des SemX & des suivis associés => est-ce utile ?
|
||||
# for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES:
|
||||
# trajectoire = self.semXs_suivis[etudid][nom_rcs]
|
||||
# if trajectoire:
|
||||
# self.rcsemxs[trajectoire.rcs_id] = trajectoire
|
||||
# self.rcsemxs_suivis[etudid][nom_rcs] = trajectoire
|
||||
|
||||
# Pour chaque aggréggat de type xA ou Sx
|
||||
tous_les_agregats = pe_rcs.TOUS_LES_RCS
|
||||
|
||||
for nom_rcs in tous_les_agregats:
|
||||
trajectoire = self.trajectoires_suivies[etudid][nom_rcs]
|
||||
# Pour chaque aggréggat de type xA ou Sx ou xS
|
||||
for agregat in pe_rcs.TOUS_LES_RCS:
|
||||
trajectoire = self.trajectoires_suivies[etudid][agregat]
|
||||
if not trajectoire:
|
||||
self.rcsemxs_suivis[etudid][nom_rcs] = None
|
||||
self.rcsemxs_suivis[etudid][agregat] = None
|
||||
else:
|
||||
# Identifiant de la trajectoire => donnera ceux du RCSemX
|
||||
tid = trajectoire.rcs_id
|
||||
@ -159,14 +155,14 @@ class RCSsJuryPE:
|
||||
# Par ex: dans S1+S2+S1+S2+S3 => les 2 S1 devient le SemX('S1'), les 2 S2 le SemX('S2'), etc..
|
||||
|
||||
# Les Sx pris en compte dans l'aggrégat
|
||||
noms_sems_aggregat = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"]
|
||||
noms_sems_aggregat = pe_rcs.TYPES_RCS[agregat]["aggregat"]
|
||||
|
||||
semxs_a_aggreger = {}
|
||||
for Sx in noms_sems_aggregat:
|
||||
semestres_etudiants = etudiants.cursus[etudid][Sx]
|
||||
semestres_etudiants = self.etudiants.cursus[etudid][Sx]
|
||||
if not semestres_etudiants:
|
||||
pas_de_semestres += [
|
||||
f"{Sx} pour {etudiants.identites[etudid].nomprenom}"
|
||||
f"{Sx} pour {self.etudiants.identites[etudid].nomprenom}"
|
||||
]
|
||||
else:
|
||||
semx_id = get_semx_from_semestres_aggreges(
|
||||
@ -184,9 +180,10 @@ class RCSsJuryPE:
|
||||
rcsemx.add_semXs(semxs_a_aggreger)
|
||||
|
||||
# Mémoire du RCSemX aux informations de suivi de l'étudiant
|
||||
self.rcsemxs_suivis[etudid][nom_rcs] = rcsemx
|
||||
self.rcsemxs_suivis[etudid][agregat] = rcsemx
|
||||
self.etudiants.rcsemXs[etudid][agregat] = rcsemx
|
||||
|
||||
# Affichage des étudiants pour lesquels il manque un semestre
|
||||
# Affichage des étudiants pour lesquels il manque un semestre
|
||||
pas_de_semestres = sorted(set(pas_de_semestres))
|
||||
if pas_de_semestres:
|
||||
pe_affichage.pe_print("⚠️ Semestres manquants :")
|
||||
@ -194,30 +191,6 @@ class RCSsJuryPE:
|
||||
"\n".join([" " * 10 + psd for psd in pas_de_semestres])
|
||||
)
|
||||
|
||||
def _aff_rcsemxs_suivis(self, etudiants):
|
||||
"""Affiche les RCSemX suivis par les étudiants"""
|
||||
# Affichage pour debug
|
||||
jeunes = list(enumerate(self.rcsemxs_suivis.keys()))
|
||||
for no_etud, etudid in jeunes:
|
||||
etat = "⛔" if etudid in etudiants.abandons_ids else "✅"
|
||||
pe_affichage.pe_print(
|
||||
f"-> {etat} {etudiants.identites[etudid].nomprenom} :"
|
||||
)
|
||||
for nom_rcs, rcs in self.rcsemxs_suivis[etudid].items():
|
||||
if rcs:
|
||||
pe_affichage.pe_print(f" > RCSemX ⏯️{nom_rcs}: {rcs.get_repr()}")
|
||||
|
||||
vides = []
|
||||
for nom_rcs in pe_rcs.TOUS_LES_RCS:
|
||||
les_rcssemX_suivis = []
|
||||
for no_etud, etudid in jeunes:
|
||||
if self.rcsemxs_suivis[etudid][nom_rcs]:
|
||||
les_rcssemX_suivis.append(self.rcsemxs_suivis[etudid][nom_rcs])
|
||||
if not les_rcssemX_suivis:
|
||||
vides += [nom_rcs]
|
||||
vides = sorted(list(set(vides)))
|
||||
pe_affichage.pe_print(f"⚠️ RCSemX vides : {', '.join(vides)}")
|
||||
|
||||
|
||||
def get_rcs_etudiant(
|
||||
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
|
||||
|
@ -38,6 +38,7 @@
|
||||
from flask import flash, g, redirect, render_template, request, send_file, url_for
|
||||
|
||||
from app.decorators import permission_required, scodoc
|
||||
from app.forms.pe.pe_sem_recap import ParametrageClasseurPE
|
||||
from app.models import FormSemestre
|
||||
from app.pe import pe_comp
|
||||
from app.pe import pe_jury
|
||||
@ -72,35 +73,51 @@ def pe_view_sem_recap(formsemestre_id: int):
|
||||
|
||||
# Cosemestres diplomants
|
||||
cosemestres = pe_comp.get_cosemestres_diplomants(annee_diplome)
|
||||
|
||||
form = ParametrageClasseurPE()
|
||||
|
||||
cosemestres_tries = pe_comp.tri_semestres_par_rang(cosemestres)
|
||||
affichage_cosemestres_tries = {rang: ", ".join([sem.titre_annee() for sem in cosemestres_tries[rang]]) for rang in cosemestres_tries}
|
||||
affichage_cosemestres_tries = {
|
||||
rang: ", ".join([sem.titre_annee() for sem in cosemestres_tries[rang]])
|
||||
for rang in cosemestres_tries
|
||||
}
|
||||
if request.method == "GET":
|
||||
return render_template(
|
||||
"pe/pe_view_sem_recap.j2",
|
||||
annee_diplome=annee_diplome,
|
||||
form=form,
|
||||
formsemestre=formsemestre,
|
||||
sco=ScoData(formsemestre=formsemestre),
|
||||
cosemestres=affichage_cosemestres_tries,
|
||||
rangs_tries=sorted(affichage_cosemestres_tries.keys())
|
||||
rangs_tries=sorted(affichage_cosemestres_tries.keys()),
|
||||
)
|
||||
|
||||
# request.method == "POST"
|
||||
jury = pe_jury.JuryPE(annee_diplome)
|
||||
if not jury.diplomes_ids:
|
||||
flash("aucun étudiant à considérer !")
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.pe_view_sem_recap",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
if form.validate_on_submit():
|
||||
jury = pe_jury.JuryPE(annee_diplome, formsemestre_id, options=form.data)
|
||||
if not jury.diplomes_ids:
|
||||
flash("aucun étudiant à considérer !")
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.pe_view_sem_recap",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
|
||||
data = jury.get_zipped_data()
|
||||
|
||||
return send_file(
|
||||
data,
|
||||
mimetype="application/zip",
|
||||
download_name=scu.sanitize_filename(jury.nom_export_zip + ".zip"),
|
||||
as_attachment=True,
|
||||
)
|
||||
|
||||
data = jury.get_zipped_data()
|
||||
|
||||
return send_file(
|
||||
data,
|
||||
mimetype="application/zip",
|
||||
download_name=scu.sanitize_filename(jury.nom_export_zip + ".zip"),
|
||||
as_attachment=True,
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends "sco_page.j2" %}
|
||||
{% import 'wtf.j2' as wtf %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
@ -42,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 (au moins) été inscrits à l'un des semestres suivants :
|
||||
|
||||
<ul>
|
||||
@ -52,24 +53,9 @@
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<progress id="pe_progress" style="visibility: hidden"></progress>
|
||||
<br>
|
||||
<button onclick="submitPEGeneration()">Générer les documents de la promo {{ annee_diplome }}</button>
|
||||
</div>
|
||||
<h3>Options</h3>
|
||||
{{ wtf.quick_form(form) }}
|
||||
|
||||
<form method="post" id="pe_generation" style="visibility: hidden">
|
||||
<input type="submit"
|
||||
onclick="submitPEGeneration()" value=""/>
|
||||
<input type="hidden" name="formsemestre_id" value="{{formsemestre.id}}">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function submitPEGeneration() {
|
||||
// document.getElementById("pe_progress").style.visibility = 'visible';
|
||||
document.getElementById("pe_generation").submit(); //attach an id to your form
|
||||
}
|
||||
</script>
|
||||
{% endblock app_content %}
|
Loading…
Reference in New Issue
Block a user