Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
5 changed files with 115 additions and 77 deletions
Showing only changes of commit b9b9a172c7 - Show all commits

View File

@ -45,10 +45,15 @@ class ParametrageClasseurPE(FlaskForm):
default=True, default=True,
render_kw={"checked": ""}, render_kw={"checked": ""},
) )
moyennes_ues_rcues = BooleanField("Générer les moyennes par RCUEs (compétences)") moyennes_ues_rcues = BooleanField(
"Générer les moyennes par RCUEs (compétences)",
default=True,
render_kw={"checked": ""},
)
min_max_moy = BooleanField("Afficher les colonnes min/max/moy") min_max_moy = BooleanField("Afficher les colonnes min/max/moy")
synthese_individuelle_etud = BooleanField( synthese_individuelle_etud = BooleanField(
"Générer la feuille synthèse avec un onglet par étudiant" "Générer les synthèses HTML étudiant par étudiant"
) )
submit = SubmitField("Générer les classeurs poursuites d'études") submit = SubmitField("Générer les classeurs poursuites d'études")

View File

@ -207,3 +207,28 @@ def repr_comp_et_ues(acronymes_ues_to_competences):
liste += ["📍" + acro] liste += ["📍" + acro]
aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"] aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"]
return "\n".join(aff_comp) 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)}")

View File

@ -69,6 +69,9 @@ class EtudiantsJuryPE:
self.semXs: dict[int:dict] = {} self.semXs: dict[int:dict] = {}
"""Les semXs (RCS de type Sx) suivis par chaque étudiant""" """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 = {} self.etudiants_diplomes = {}
"""Les identités des étudiants à considérer au jury (ceux qui seront effectivement """Les identités des étudiants à considérer au jury (ceux qui seront effectivement
diplômés)""" diplômés)"""
@ -274,6 +277,7 @@ class EtudiantsJuryPE:
# Initialise ses trajectoires/SemX/RCSemX # Initialise ses trajectoires/SemX/RCSemX
self.trajectoires[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS} 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.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): def structure_cursus_etudiant(self, etudid: int):
"""Structure les informations sur les semestres suivis par un """Structure les informations sur les semestres suivis par un

View File

@ -102,9 +102,8 @@ class JuryPE(object):
"Nom du zip où ranger les fichiers générés" "Nom du zip où ranger les fichiers générés"
# Les options # Les options
self.options = options self.options = options
"""Options de configuration""" """Options de configuration (cf. pe_sem_recap)"""
pe_affichage.pe_print( pe_affichage.pe_print(
f"Données de poursuite d'étude générées le {time.strftime('%d/%m/%Y à %H:%M')}\n", f"Données de poursuite d'étude générées le {time.strftime('%d/%m/%Y à %H:%M')}\n",
@ -350,8 +349,9 @@ class JuryPE(object):
pe_affichage.pe_print( pe_affichage.pe_print(
"""******************************************************************************""" """******************************************************************************"""
) )
self.rcss_jury.cree_rcsemxs(self.etudiants) self.rcss_jury.cree_rcsemxs(options=self.options)
self.rcss_jury._aff_rcsemxs_suivis(self.etudiants) 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): def _gen_xls_rcstags(self, zipfile: ZipFile):
"""Génère les RCS taggués traduisant les moyennes (orientées compétences) """Génère les RCS taggués traduisant les moyennes (orientées compétences)
@ -384,6 +384,20 @@ class JuryPE(object):
) )
pe_affichage.pe_print("1) Calcul des moyennes des RCSTag", info=True) 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 = {} self.rcsstags = {}
for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items(): for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items():
self.rcsstags[rcs_id] = pe_rcstag.RCSemXTag( self.rcsstags[rcs_id] = pe_rcstag.RCSemXTag(
@ -431,20 +445,26 @@ class JuryPE(object):
"""******************************************************************""" """******************************************************************"""
) )
pe_affichage.pe_print( 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( pe_affichage.pe_print(
"""******************************************************************""" """******************************************************************"""
) )
self.interclasstags = { if (
pe_moytag.CODE_MOY_UE: {}, "moyennes_ues_rcues" not in self.options
pe_moytag.CODE_MOY_COMPETENCES: {}, 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 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: for Sx in pe_rcs.TOUS_LES_SEMESTRES:
interclass = pe_interclasstag.InterClassTag( interclass = pe_interclasstag.InterClassTag(
Sx, Sx,
@ -457,16 +477,22 @@ class JuryPE(object):
self.interclasstags[pe_moytag.CODE_MOY_UE][Sx] = interclass self.interclasstags[pe_moytag.CODE_MOY_UE][Sx] = interclass
# Les interclassements par compétences # Les interclassements par compétences
for nom_rcs in pe_rcs.TOUS_LES_RCS: if (
interclass = pe_interclasstag.InterClassTag( "moyennes_ues_rcues" not in self.options
nom_rcs, or self.options["moyennes_ues_rcues"]
pe_moytag.CODE_MOY_COMPETENCES, ):
etudiants_diplomes, for nom_rcs in pe_rcs.TOUS_LES_RCS:
self.rcss_jury.rcsemxs, interclass = pe_interclasstag.InterClassTag(
self.rcsstags, nom_rcs,
self.rcss_jury.rcsemxs_suivis, pe_moytag.CODE_MOY_COMPETENCES,
) etudiants_diplomes,
self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][nom_rcs] = interclass 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 # Intègre le bilan des aggrégats (interclassé par promo) au zip final
output = io.BytesIO() output = io.BytesIO()
@ -474,10 +500,9 @@ class JuryPE(object):
output, engine="openpyxl" output, engine="openpyxl"
) as writer: ) as writer:
onglets = [] onglets = []
for type_interclass in [ for (
pe_moytag.CODE_MOY_UE, type_interclass
pe_moytag.CODE_MOY_COMPETENCES, ) in self.interclasstags: # Pour les types d'interclassements prévus
]:
interclasstag = self.interclasstags[type_interclass] interclasstag = self.interclasstags[type_interclass]
for nom_rcs, interclass in interclasstag.items(): for nom_rcs, interclass in interclasstag.items():
if interclass.is_significatif(): if interclass.is_significatif():
@ -522,7 +547,7 @@ class JuryPE(object):
tags = self._do_tags_list(self.interclasstags) tags = self._do_tags_list(self.interclasstags)
for tag in tags: 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) self.synthese[(tag, type_moy)] = self.df_tag_type(tag, type_moy)
# Export des données => mode 1 seule feuille -> supprimé # Export des données => mode 1 seule feuille -> supprimé
@ -555,7 +580,7 @@ class JuryPE(object):
if onglets: if onglets:
self.add_file_to_zip( self.add_file_to_zip(
zipfile, f"synthese_jury_{self.diplome}_par_tag.xlsx", output.read() zipfile, f"synthese_jury_{self.diplome}.xlsx", output.read()
) )
def _gen_html_synthese_par_etudiant(self, zipfile: ZipFile): def _gen_html_synthese_par_etudiant(self, zipfile: ZipFile):
@ -565,12 +590,18 @@ class JuryPE(object):
pe_affichage.pe_print("*** Synthèse finale étudiant par étudiant", info=True) pe_affichage.pe_print("*** Synthèse finale étudiant par étudiant", info=True)
pe_affichage.pe_print("**************************************************") pe_affichage.pe_print("**************************************************")
etudids = list(self.diplomes_ids) if (
for etudid in etudids: "moyennes_ues_rcues" not in self.options
nom, prenom, html = self.synthetise_jury_etudiant(etudid) or self.options["moyennes_ues_rcues"]
self.add_file_to_zip( ):
zipfile, f"{nom}_{prenom}.html", html, path="etudiants" 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): def _add_log_to_zip(self, zipfile):
"""Add a text file with the log messages""" """Add a text file with the log messages"""

View File

@ -117,7 +117,7 @@ class RCSsJuryPE:
self.semXs_suivis[etudid][agregat] = semX self.semXs_suivis[etudid][agregat] = semX
self.etudiants.semXs[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 """Créé tous les RCSemXs, au regard du cursus des étudiants
analysés (trajectoires traduisant son parcours dans les analysés (trajectoires traduisant son parcours dans les
différents semestres) + les mémorise dans les données de l'étudiant différents semestres) + les mémorise dans les données de l'étudiant
@ -125,6 +125,11 @@ class RCSsJuryPE:
self.rcsemxs_suivis = {} self.rcsemxs_suivis = {}
self.rcsemxs = {} 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 # Pour tous les étudiants du jury
pas_de_semestres = [] pas_de_semestres = []
for etudid in self.trajectoires_suivies: 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 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 ? # Pour chaque aggréggat de type xA ou Sx ou xS
# for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES: for agregat in pe_rcs.TOUS_LES_RCS:
# trajectoire = self.semXs_suivis[etudid][nom_rcs] trajectoire = self.trajectoires_suivies[etudid][agregat]
# 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]
if not trajectoire: if not trajectoire:
self.rcsemxs_suivis[etudid][nom_rcs] = None self.rcsemxs_suivis[etudid][agregat] = None
else: else:
# Identifiant de la trajectoire => donnera ceux du RCSemX # Identifiant de la trajectoire => donnera ceux du RCSemX
tid = trajectoire.rcs_id 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.. # 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 # 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 = {} semxs_a_aggreger = {}
for Sx in noms_sems_aggregat: for Sx in noms_sems_aggregat:
semestres_etudiants = etudiants.cursus[etudid][Sx] semestres_etudiants = self.etudiants.cursus[etudid][Sx]
if not semestres_etudiants: if not semestres_etudiants:
pas_de_semestres += [ pas_de_semestres += [
f"{Sx} pour {etudiants.identites[etudid].nomprenom}" f"{Sx} pour {self.etudiants.identites[etudid].nomprenom}"
] ]
else: else:
semx_id = get_semx_from_semestres_aggreges( semx_id = get_semx_from_semestres_aggreges(
@ -184,9 +180,10 @@ class RCSsJuryPE:
rcsemx.add_semXs(semxs_a_aggreger) rcsemx.add_semXs(semxs_a_aggreger)
# Mémoire du RCSemX aux informations de suivi de l'étudiant # 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)) pas_de_semestres = sorted(set(pas_de_semestres))
if pas_de_semestres: if pas_de_semestres:
pe_affichage.pe_print("⚠️ Semestres manquants :") pe_affichage.pe_print("⚠️ Semestres manquants :")
@ -194,30 +191,6 @@ class RCSsJuryPE:
"\n".join([" " * 10 + psd for psd in pas_de_semestres]) "\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( def get_rcs_etudiant(
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str