forked from ScoDoc/ScoDoc
456 lines
18 KiB
Python
456 lines
18 KiB
Python
# -*- pole: 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 Generfal 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
|
|
"""
|
|
import pandas as pd
|
|
|
|
from app import ScoValueError
|
|
from app import comp
|
|
from app.comp.res_but import ResultatsSemestreBUT
|
|
from app.models import FormSemestre, UniteEns
|
|
import app.pe.pe_affichage as pe_affichage
|
|
import app.pe.pe_etudiant as pe_etudiant
|
|
from app.pe.moys import pe_tabletags, pe_moytag
|
|
from app.scodoc import sco_tag_module
|
|
from app.scodoc import codes_cursus as sco_codes
|
|
from app.scodoc.sco_utils import *
|
|
|
|
|
|
class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
|
"""
|
|
Un ResSemBUTTag représente les résultats des étudiants à un semestre, en donnant
|
|
accès aux moyennes par tag.
|
|
Il s'appuie principalement sur un ResultatsSemestreBUT.
|
|
"""
|
|
|
|
def __init__(self, formsemestre: FormSemestre, options={}):
|
|
"""
|
|
Args:
|
|
formsemestre: le ``FormSemestre`` sur lequel il se base
|
|
options: Un dictionnaire d'options
|
|
"""
|
|
ResultatsSemestreBUT.__init__(self, formsemestre)
|
|
pe_tabletags.TableTag.__init__(self)
|
|
|
|
# Le nom du res_semestre taggué
|
|
self.nom = self.get_repr(verbose=True)
|
|
|
|
# Les étudiants (etuds, états civils & etudis) ajouté
|
|
self.add_etuds(self.etuds)
|
|
self.etudids_sorted = sorted(self.etudids)
|
|
"""Les etudids des étudiants du ResultatsSemestreBUT triés"""
|
|
|
|
pe_affichage.pe_print(
|
|
f"*** ResSemBUTTag du {self.nom} => {len(self.etudids_sorted)} étudiants"
|
|
)
|
|
|
|
# Les UEs (et les dispenses d'UE)
|
|
self.ues_standards: list[UniteEns] = [
|
|
ue for ue in self.ues if ue.type == sco_codes.UE_STANDARD
|
|
]
|
|
"""Liste des UEs standards du ResultatsSemestreBUT"""
|
|
|
|
# Les parcours des étudiants à ce semestre
|
|
self.parcours = []
|
|
"""Parcours auxquels sont inscrits les étudiants"""
|
|
for etudid in self.etudids_sorted:
|
|
parcour = self.formsemestre.etuds_inscriptions[etudid].parcour
|
|
if parcour:
|
|
self.parcours += [parcour.libelle]
|
|
else:
|
|
self.parcours += [None]
|
|
|
|
# Les UEs en fonction des parcours
|
|
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
|
|
"""Inscription des étudiants aux UEs des parcours"""
|
|
|
|
# Les acronymes des UEs
|
|
self.ues_to_acronymes = {ue.id: ue.acronyme for ue in self.ues_standards}
|
|
self.acronymes_sorted = sorted(self.ues_to_acronymes.values())
|
|
"""Les acronymes de UE triés par ordre alphabétique"""
|
|
|
|
# Les compétences associées aux UEs (définies par les acronymes)
|
|
self.acronymes_ues_to_competences = {}
|
|
"""Association acronyme d'UEs -> compétence"""
|
|
for ue in self.ues_standards:
|
|
assert ue.niveau_competence, ScoValueError(
|
|
"Des UEs ne sont pas rattachées à des compétences"
|
|
)
|
|
nom = ue.niveau_competence.competence.titre
|
|
self.acronymes_ues_to_competences[ue.acronyme] = nom
|
|
self.competences_sorted = sorted(
|
|
list(set(self.acronymes_ues_to_competences.values()))
|
|
)
|
|
"""Compétences triées par nom"""
|
|
aff = pe_affichage.repr_asso_ue_comp(self.acronymes_ues_to_competences)
|
|
pe_affichage.pe_print(f"--> UEs/Compétences : {aff}")
|
|
|
|
# Les tags personnalisés et auto:
|
|
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)}"""
|
|
)
|
|
self._check_tags(tags_dict)
|
|
|
|
# Les coefficients pour le calcul de la moyenne générale, donnés par
|
|
# acronymes d'UE
|
|
self.matrice_coeffs_moy_gen = self._get_matrice_coeffs(
|
|
self.ues_inscr_parcours_df, self.ues_standards
|
|
)
|
|
"""DataFrame indiquant les coeffs des UEs par ordre alphabétique d'acronyme"""
|
|
profils_aff = pe_affichage.repr_profil_coeffs(self.matrice_coeffs_moy_gen)
|
|
pe_affichage.pe_print(
|
|
f"--> Moyenne générale calculée avec pour coeffs d'UEs : {profils_aff}"
|
|
)
|
|
|
|
# Les capitalisations (mask etuids x acronyme_ue valant True si capitalisée, False sinon)
|
|
self.capitalisations = self._get_capitalisations(self.ues_standards)
|
|
"""DataFrame indiquant les UEs capitalisables d'un étudiant (etudids x )"""
|
|
|
|
# Calcul des moyennes & les classements de chaque étudiant à chaque tag
|
|
self.moyennes_tags = {}
|
|
"""Moyennes par tags (personnalisés ou 'but')"""
|
|
for tag in tags_dict["personnalises"]:
|
|
# pe_affichage.pe_print(f" -> Traitement du tag {tag}")
|
|
info_tag = tags_dict["personnalises"][tag]
|
|
# Les moyennes générales par UEs
|
|
moy_ues_tag = self.compute_moy_ues_tag(info_tag=info_tag, pole=None)
|
|
# Mémorise les moyennes
|
|
self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
|
|
tag,
|
|
pe_moytag.CODE_MOY_UE,
|
|
moy_ues_tag,
|
|
self.matrice_coeffs_moy_gen,
|
|
)
|
|
|
|
# Ajoute les moyennes par UEs + la moyenne générale (but)
|
|
moy_gen = self.compute_moy_gen()
|
|
self.moyennes_tags["but"] = pe_moytag.MoyennesTag(
|
|
"but",
|
|
pe_moytag.CODE_MOY_UE,
|
|
moy_gen,
|
|
self.matrice_coeffs_moy_gen,
|
|
)
|
|
|
|
# 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,
|
|
)
|
|
|
|
# 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,
|
|
)
|
|
|
|
# Tous les tags
|
|
self.tags_sorted = self.get_all_significant_tags()
|
|
"""Tags (personnalisés+compétences) par ordre alphabétique"""
|
|
|
|
def get_repr(self, verbose=False) -> str:
|
|
"""Nom affiché pour le semestre taggué, de la forme (par ex.):
|
|
|
|
* S1#69 si verbose est False
|
|
* S1 FI 2023 si verbose est True
|
|
"""
|
|
if not verbose:
|
|
return f"{self.formsemestre}#{self.formsemestre.formsemestre_id}"
|
|
else:
|
|
return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
|
|
|
|
def _get_matrice_coeffs(
|
|
self, ues_inscr_parcours_df: pd.DataFrame, ues_standards: list[UniteEns]
|
|
) -> pd.DataFrame:
|
|
"""Renvoie un dataFrame donnant les coefficients à appliquer aux UEs
|
|
dans le calcul de la moyenne générale (toutes UEs confondues).
|
|
Prend en compte l'inscription des étudiants aux UEs en fonction de leur parcours
|
|
(cf. ues_inscr_parcours_df).
|
|
|
|
Args:
|
|
ues_inscr_parcours_df: Les inscriptions des étudiants aux UEs
|
|
ues_standards: Les UEs standards à prendre en compte
|
|
|
|
Returns:
|
|
Un dataFrame etudids x acronymes_UEs avec les coeffs des UEs
|
|
"""
|
|
matrice_coeffs_moy_gen = ues_inscr_parcours_df * [
|
|
ue.ects for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé
|
|
]
|
|
matrice_coeffs_moy_gen.columns = [
|
|
self.ues_to_acronymes[ue.id] for ue in ues_standards
|
|
]
|
|
# Tri par etudids (dim 0) et par acronymes (dim 1)
|
|
matrice_coeffs_moy_gen = matrice_coeffs_moy_gen.sort_index()
|
|
matrice_coeffs_moy_gen = matrice_coeffs_moy_gen.sort_index(axis=1)
|
|
return matrice_coeffs_moy_gen
|
|
|
|
def _get_capitalisations(self, ues_standards) -> pd.DataFrame:
|
|
"""Renvoie un dataFrame résumant les UEs capitalisables par les
|
|
étudiants, d'après les décisions de jury (sous réserve qu'elles existent).
|
|
|
|
Args:
|
|
ues_standards: Liste des UEs standards (notamment autres que le sport)
|
|
Returns:
|
|
Un dataFrame etudids x acronymes_UEs dont les valeurs sont ``True`` si l'UE
|
|
est capitalisable, ``False`` sinon
|
|
"""
|
|
capitalisations = pd.DataFrame(
|
|
False, index=self.etudids_sorted, columns=self.acronymes_sorted
|
|
)
|
|
self.get_formsemestre_validations() # charge les validations
|
|
res_jury = self.validations
|
|
if res_jury:
|
|
for etud in self.etuds:
|
|
etudid = etud.etudid
|
|
decisions = res_jury.decisions_jury_ues.get(etudid, {})
|
|
for ue in ues_standards:
|
|
if ue.id in decisions and decisions[ue.id]["code"] == sco_codes.ADM:
|
|
capitalisations.loc[etudid, ue.acronyme] = True
|
|
# Tri par etudis et par accronyme d'UE
|
|
capitalisations = capitalisations.sort_index()
|
|
capitalisations = capitalisations.sort_index(axis=1)
|
|
return capitalisations
|
|
|
|
def compute_moy_ues_tag(
|
|
self, info_tag: dict[int, dict] = None, pole=None
|
|
) -> pd.DataFrame:
|
|
"""Calcule la moyenne par UE des étudiants pour un tag donné,
|
|
en ayant connaissance des informations sur le tag.
|
|
|
|
info_tag détermine les modules pris en compte :
|
|
* si non `None`, seuls les modules rattachés au tag sont pris en compte
|
|
* si `None`, tous les modules (quelque soit leur rattachement au tag) sont pris
|
|
en compte (sert au calcul de la moyenne générale par ressource ou SAE)
|
|
|
|
`pole` détermine les modules pris en compte :
|
|
|
|
* si `pole` vaut `ModuleType.RESSOURCE`, seules les ressources sont prises
|
|
en compte (moyenne de ressources par UEs)
|
|
* si `pole` vaut `ModuleType.SAE`, seules les SAEs sont prises en compte
|
|
* si `pole` vaut `None` (ou toute autre valeur),
|
|
tous les modules sont pris en compte (moyenne d'UEs)
|
|
|
|
|
|
Les informations sur le tag sont un dictionnaire listant les modimpl_id rattachés au tag,
|
|
et pour chacun leur éventuel coefficient de **repondération**.
|
|
|
|
Returns:
|
|
Le dataframe des moyennes du tag par UE
|
|
"""
|
|
modimpls_sorted = self.formsemestre.modimpls_sorted
|
|
|
|
# Adaptation du mask de calcul des moyennes au tag visé
|
|
modimpls_mask = []
|
|
for modimpl in modimpls_sorted:
|
|
module = modimpl.module # Le module
|
|
mask = module.ue.type == sco_codes.UE_STANDARD # Est-ce une UE stantard ?
|
|
if pole == ModuleType.RESSOURCE:
|
|
mask &= module.module_type == ModuleType.RESSOURCE
|
|
elif pole == ModuleType.SAE:
|
|
mask &= module.module_type == ModuleType.SAE
|
|
modimpls_mask += [mask]
|
|
|
|
# Prise en compte du tag
|
|
if info_tag:
|
|
# Désactive tous les modules qui ne sont pas pris en compte pour ce tag
|
|
for i, modimpl in enumerate(modimpls_sorted):
|
|
if modimpl.moduleimpl_id not in info_tag:
|
|
modimpls_mask[i] = False
|
|
|
|
# Applique la pondération des coefficients
|
|
modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy()
|
|
if info_tag:
|
|
for modimpl_id in info_tag:
|
|
ponderation = info_tag[modimpl_id]["ponderation"]
|
|
modimpl_coefs_ponderes_df[modimpl_id] *= ponderation
|
|
|
|
# Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)
|
|
moyennes_ues_tag = comp.moy_ue.compute_ue_moys_apc(
|
|
self.sem_cube,
|
|
self.etuds,
|
|
self.formsemestre.modimpls_sorted,
|
|
self.modimpl_inscr_df,
|
|
modimpl_coefs_ponderes_df,
|
|
modimpls_mask,
|
|
self.dispense_ues,
|
|
block=self.formsemestre.block_moyennes,
|
|
)
|
|
|
|
# Ne conserve que les UEs standards
|
|
colonnes = [ue.id for ue in self.ues_standards]
|
|
moyennes_ues_tag = moyennes_ues_tag[colonnes]
|
|
|
|
# Transforme les UEs en acronyme
|
|
acronymes = [self.ues_to_acronymes[ue.id] for ue in self.ues_standards]
|
|
moyennes_ues_tag.columns = acronymes
|
|
|
|
# Tri par etudids et par ordre alphabétique d'acronyme
|
|
moyennes_ues_tag = moyennes_ues_tag.sort_index()
|
|
moyennes_ues_tag = moyennes_ues_tag.sort_index(axis=1)
|
|
|
|
return moyennes_ues_tag
|
|
|
|
def compute_moy_gen(self):
|
|
"""Récupère les moyennes des UEs pour le calcul de la moyenne générale,
|
|
en associant à chaque UE.id son acronyme (toutes UEs confondues)
|
|
"""
|
|
df_ues = pd.DataFrame(
|
|
{ue.id: self.etud_moy_ue[ue.id] for ue in self.ues_standards},
|
|
index=self.etudids,
|
|
)
|
|
# Transforme les UEs en acronyme
|
|
colonnes = df_ues.columns
|
|
acronymes = [self.ues_to_acronymes[col] for col in colonnes]
|
|
df_ues.columns = acronymes
|
|
|
|
# Tri par ordre aphabétique de colonnes
|
|
df_ues.sort_index(axis=1)
|
|
|
|
return df_ues
|
|
|
|
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 :
|
|
|
|
``{"personnalises": {tag: info_sur_le_tag},
|
|
"auto": {tag: {}}``
|
|
|
|
Returns:
|
|
Le dictionnaire structuré des tags ("personnalises" vs. "auto")
|
|
"""
|
|
dict_tags = {"personnalises": dict(), "auto": dict()}
|
|
|
|
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
|
|
# dict_ues_competences = get_noms_competences_from_ues(self.nt.formsemestre)
|
|
# noms_tags_comp = list(set(dict_ues_competences.values()))
|
|
|
|
# BUT
|
|
dict_tags["auto"] = {"but": {}, "ressources": {}, "saes": {}}
|
|
return dict_tags
|
|
|
|
def _check_tags(self, dict_tags):
|
|
"""Vérifie l'unicité des tags"""
|
|
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
|
|
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
|
|
noms_tags = noms_tags_perso + noms_tags_auto
|
|
|
|
intersection = list(set(noms_tags_perso) & set(noms_tags_auto))
|
|
|
|
if intersection:
|
|
liste_intersection = "\n".join(
|
|
[f"<li><code>{tag}</code></li>" for tag in intersection]
|
|
)
|
|
s = "s" if len(intersection) > 1 else ""
|
|
message = f"""Erreur dans le module PE : Un des tags saisis dans votre
|
|
programme de formation fait parti des tags réservés. En particulier,
|
|
votre semestre <em>{self.formsemestre.titre_annee()}</em>
|
|
contient le{s} tag{s} réservé{s} suivant :
|
|
<ul>
|
|
{liste_intersection}
|
|
</ul>
|
|
Modifiez votre programme de formation pour le{s} supprimer.
|
|
Il{s} ser{'ont' if s else 'a'} automatiquement à vos documents de poursuites d'études.
|
|
"""
|
|
raise ScoValueError(message)
|
|
|
|
|
|
def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
|
|
"""Etant données les implémentations des modules du semestre (modimpls),
|
|
synthétise les tags renseignés dans le programme pédagogique &
|
|
associés aux modules du semestre,
|
|
en les associant aux modimpls qui les concernent (modimpl_id) et
|
|
au coeff de repondération fournie avec le tag (par défaut 1 si non indiquée)).
|
|
|
|
Le dictionnaire fournit est de la forme :
|
|
|
|
``{ tag : { modimplid: {"modimpl": ModImpl,
|
|
"ponderation": coeff_de_reponderation}
|
|
} }``
|
|
|
|
Args:
|
|
formsemestre: Le formsemestre à la base de la recherche des tags
|
|
|
|
Return:
|
|
Un dictionnaire décrivant les tags
|
|
"""
|
|
synthese_tags = {}
|
|
|
|
# Instance des modules du semestre
|
|
modimpls = formsemestre.modimpls_sorted
|
|
|
|
for modimpl in modimpls:
|
|
modimpl_id = modimpl.id
|
|
|
|
# Liste des tags pour le module concerné
|
|
tags = sco_tag_module.module_tag_list(modimpl.module.id)
|
|
|
|
# Traitement des tags recensés, chacun pouvant étant de la forme
|
|
# "mathématiques", "théorie", "pe:0", "maths:2"
|
|
for tag in tags:
|
|
# Extraction du nom du tag et du coeff de pondération
|
|
(tagname, ponderation) = sco_tag_module.split_tagname_coeff(tag)
|
|
|
|
# Ajout d'une clé pour le tag
|
|
if tagname not in synthese_tags:
|
|
synthese_tags[tagname] = {}
|
|
|
|
# Ajout du module (modimpl) au tagname considéré
|
|
synthese_tags[tagname][modimpl_id] = {
|
|
"modimpl": modimpl, # les données sur le module
|
|
"ponderation": ponderation, # la pondération demandée pour le tag sur le module
|
|
}
|
|
|
|
return synthese_tags
|