ScoDoc/app/pe/pe_ressemtag.py

332 lines
13 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
"""
import pandas as pd
from app import db, ScoValueError
from app import comp
from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre
from app.models.moduleimpls import ModuleImpl
import app.pe.pe_affichage as pe_affichage
import app.pe.pe_etudiant as pe_etudiant
import app.pe.pe_tabletags as pe_tabletags
from app.pe.pe_moytag import MoyennesTag
from app.scodoc import sco_tag_module
from app.scodoc import codes_cursus as sco_codes
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 FormSemestre et sur ResultatsSemestreBUT.
"""
def __init__(self, formsemestre: FormSemestre):
"""
Args:
formsemestre: le ``FormSemestre`` sur lequel il se base
"""
ResultatsSemestreBUT.__init__(self, formsemestre)
pe_tabletags.TableTag.__init__(self)
# Le nom du res_semestre taggué
self.nom = self.get_repr(verbose=True)
pe_affichage.pe_print(f"--> Résultats de semestre taggués {self.nom}")
# Les étudiants (etuds, états civils & etudis) ajouté
self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids)
# Les UEs (et les dispenses d'UE)
# self.ues
ues_standards = [ue for ue in self.ues if ue.type == sco_codes.UE_STANDARD]
# Les UEs en fonction des parcours
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
# Les compétences associées aux UEs (définies par les acronymes)
self.competences = {}
"""L'association acronyme d'UEs -> compétence"""
for ue in self.ues:
if ue.type == sco_codes.UE_STANDARD:
assert ue.niveau_competence, ScoValueError(
"Des UEs ne sont pas rattachées à des compétences"
)
nom = ue.niveau_competence.competence.titre
self.competences[ue.acronyme] = nom
# Les acronymes des UEs
self.ues_to_acronymes = {ue.id: ue.acronyme for ue in ues_standards}
self.acronymes_sorted = sorted(self.ues_to_acronymes.values())
"""Les acronymes de UE triés par ordre alphabétique"""
# Les tags personnalisés et auto:
tags_dict = self._get_tags_dict()
self._check_tags(tags_dict)
# Les coefficients pour le calcul de la moyenne générale
self.matrice_coeffs_moy_gen = self.ues_inscr_parcours_df * [
ue.ects for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé
]
# Les capitalisations (mask etuids x acronyme_ue valant True si capitalisée, False sinon)
self.capitalisations = self._get_capitalisations(ues_standards)
# Calcul des moyennes & les classements de chaque étudiant à chaque tag
self.moyennes_tags = {}
for tag in tags_dict["personnalises"]:
# pe_affichage.pe_print(f" -> Traitement du tag {tag}")
infos_tag = tags_dict["personnalises"][tag]
moy_ues_tag = self.compute_moy_ues_tag(infos_tag)
self.moyennes_tags[tag] = MoyennesTag(
tag, moy_ues_tag, self.matrice_coeffs_moy_gen
)
# Ajoute les moyennes par UEs + la moyenne générale (but)
df_ues = pd.DataFrame(
{ue.id: self.etud_moy_ue[ue.id] for ue in 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
self.moyennes_tags["but"] = MoyennesTag(
"but", df_ues, self.matrice_coeffs_moy_gen # , moy_gen_but
)
self.tags_sorted = self.get_all_tags()
"""Tags (personnalisés+compétences) par ordre alphabétique"""
def get_repr(self, verbose=False):
"""Nom affiché pour le semestre taggué"""
if verbose:
return f"{self.formsemestre} (#{self.formsemestre.formsemestre_id})"
else:
return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
def _get_capitalisations(self, ues_hors_sport) -> pd.DataFrame:
"""Renvoie un dataFrame résumant les UEs capitalisables par les
étudiants, d'après les décisions de jury
Args:
ues_hors_sport: Liste des UEs autres que le sport
"""
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_hors_sport:
if ue.id in decisions and decisions[ue.id]["code"] == sco_codes.ADM:
capitalisations.loc[etudid, ue.acronyme] = True
# pe_affichage.pe_print(
# f" ⚠ Capitalisation de {ue.acronyme} pour {etud.etat_civil}"
# )
return capitalisations
def compute_moy_ues_tag(self, info_tag: dict[int, dict]) -> pd.DataFrame:
"""Calcule la moyenne par UE des étudiants pour un tag,
en ayant connaissance des informations sur le tag.
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
"""
# Adaptation du mask de calcul des moyennes au tag visé
modimpls_mask = [
modimpl.module.ue.type == sco_codes.UE_STANDARD
for modimpl in self.formsemestre.modimpls_sorted
]
# Désactive tous les modules qui ne sont pas pris en compte pour ce tag
for i, modimpl in enumerate(self.formsemestre.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()
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,
)
# Transforme les UEs en acronyme
colonnes = moyennes_ues_tag.columns
ue_to_acro = {ue.id: ue.acronyme for ue in self.ues}
acronymes = [ue_to_acro[col] for col in colonnes]
moyennes_ues_tag.columns = acronymes
return moyennes_ues_tag
def _get_tags_dict(self):
"""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()}
# Les tags perso
dict_tags["personnalises"] = get_synthese_tags_personnalises_semestre(
self.formsemestre
)
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
# 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": {}}
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
# Affichage
pe_affichage.pe_print(
f"* Tags du programme de formation : {', '.join(noms_tags_perso)} "
+ f"Tags automatiques : {', '.join(noms_tags_auto)}"
)
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
# "coeff": modimpl.module.coefficient, # le coeff du module dans le semestre
"ponderation": ponderation, # la pondération demandée pour le tag sur le module
# "module_code": modimpl.module.code, # le code qui doit se retrouver à l'identique dans des ue capitalisee
# "ue_id": modimpl.module.ue.id, # les données sur l'ue
# "ue_code": modimpl.module.ue.ue_code,
# "ue_acronyme": modimpl.module.ue.acronyme,
}
return synthese_tags