ScoDoc/app/pe/moys/pe_interclasstag.py

343 lines
13 KiB
Python

##############################################################################
#
# 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 Thu Sep 8 09:36:33 2016
@author: barasc
"""
import pandas as pd
import numpy as np
from app.models import Identite
from app.pe import pe_affichage
from app.pe.moys import pe_tabletags, pe_moy, pe_moytag, pe_sxtag
from app.pe.rcss import pe_rcs
import app.pe.pe_comp as pe_comp
from app.scodoc.sco_utils import ModuleType
class InterClassTag(pe_tabletags.TableTag):
"""
Interclasse l'ensemble des étudiants diplômés à une année
donnée (celle du jury), pour un RCS donné (par ex: 'S2', '3S'), qu'il soit
de type SemX ou RCSemX,
en reportant les moyennes obtenues sur à la version tagguée
du RCS (de type SxTag ou RCSTag).
Sont ensuite calculés les classements (uniquement)
sur les étudiants diplômes.
Args:
nom_rcs: Le nom de l'aggrégat
type_interclassement: Le type d'interclassement (par UE ou par compétences)
etudiants_diplomes: L'identité des étudiants diplômés
rcss: Un dictionnaire {(nom_rcs, fid_final): RCS} donnant soit
les SemX soit les RCSemX recencés par le jury PE
rcstag: Un dictionnaire {(nom_rcs, fid_final): RCSTag} donnant
soit les SxTag (associés aux SemX)
soit les RCSTags (associés au RCSemX) calculés par le jury PE
suivis: Un dictionnaire associé à chaque étudiant son rcss
(de la forme ``{etudid: {nom_rcs: RCS_suivi}}``)
"""
def __init__(
self,
nom_rcs: str,
type_interclassement: str,
etudiants_diplomes: dict[int, Identite],
rcss: dict[(str, int) : pe_rcs.RCS],
rcstags: dict[(str, int) : pe_tabletags.TableTag],
suivis: dict[int:dict],
):
pe_tabletags.TableTag.__init__(self)
self.nom_rcs: str = nom_rcs
"""Le nom du RCS interclassé"""
# Le type d'interclassement
self.type = type_interclassement
pe_affichage.pe_print(
f"*** Interclassement par 🗂️{type_interclassement} pour le RCS ⏯️{nom_rcs}"
)
# Les informations sur les étudiants diplômés
self.etuds: list[Identite] = list(etudiants_diplomes.values())
"""Identités des étudiants diplômés"""
self.add_etuds(self.etuds)
self.diplomes_ids = set(etudiants_diplomes.keys())
"""Etudids des étudiants diplômés"""
# Les RCS de l'aggrégat (SemX ou RCSemX)
self.rcss: dict[(str, int), pe_rcs.RCS] = {}
"""Ensemble des SemX ou des RCSemX associés à l'aggrégat"""
for (nom, fid), rcs in rcss.items():
if nom == nom_rcs:
self.rcss[(nom, fid)] = rcss
# Les données tagguées
self.rcstags: dict[(str, int), pe_tabletags.TableTag] = {}
"""Ensemble des SxTag ou des RCSTags associés à l'aggrégat"""
for rcs_id in self.rcss:
self.rcstags[rcs_id] = rcstags[rcs_id]
# Les RCS (SemX ou RCSemX) suivis par les étudiants du jury,
# en ne gardant que ceux associés aux diplomés
self.suivis: dict[int, pe_rcs.RCS] = {}
"""Association entre chaque étudiant et le SxTag ou RCSTag à prendre
pour l'aggrégat"""
for etudid in self.diplomes_ids:
self.suivis[etudid] = suivis[etudid][nom_rcs]
# Les données sur les tags
self.tags_sorted = self._do_taglist()
"""Liste des tags (triés par ordre alphabétique)"""
aff = pe_affichage.repr_tags(self.tags_sorted)
pe_affichage.pe_print(f"--> Tags : {aff}")
# Les données sur les UEs (si SxTag) ou compétences (si RCSTag)
self.champs_sorted = self._do_ues_ou_competences_list()
"""Les champs (UEs ou compétences) de l'interclassement"""
if self.type == pe_moytag.CODE_MOY_UE:
pe_affichage.pe_print(
f"--> UEs : {pe_affichage.aff_UEs(self.champs_sorted)}"
)
else:
pe_affichage.pe_print(
f"--> Compétences : {pe_affichage.aff_competences(self.champs_sorted)}"
)
# Etudids triés
self.etudids_sorted = sorted(list(self.diplomes_ids))
self.nom = self.get_repr()
"""Représentation textuelle de l'interclassement"""
# Synthétise les moyennes/classements par tag
self.moyennes_tags: dict[str, pe_moytag.MoyennesTag] = {}
for tag in self.tags_sorted:
# Les moyennes tous modules confondus
notes_gen = self.compute_notes_matrice(tag)
# Les coefficients de la moyenne générale
coeffs = self.compute_coeffs_matrice(tag)
aff = pe_affichage.repr_profil_coeffs(coeffs, with_index=True)
pe_affichage.pe_print(f"--> Moyenne 👜{tag} avec coeffs: {aff} ")
self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag,
self.type,
notes_gen,
coeffs, # limite les moyennes aux étudiants de la promo
)
def get_repr(self) -> str:
"""Une représentation textuelle"""
return f"{self.nom_rcs} par {self.type}"
def _do_taglist(self):
"""Synthétise les tags à partir des TableTags (SXTag ou RCSTag)
Returns:
Une liste de tags triés par ordre alphabétique
"""
tags = []
for rcstag in self.rcstags.values():
tags.extend(rcstag.tags_sorted)
return sorted(set(tags))
def compute_notes_matrice(self, tag) -> pd.DataFrame:
"""Construit la matrice de notes (etudids x champs) en
reportant les moyennes obtenues par les étudiants
aux semestres de l'aggrégat pour le tag visé.
Les champs peuvent être des acronymes d'UEs ou des compétences.
Args:
tag: Le tag visé
Return:
Le dataFrame (etudids x champs)
reportant les moyennes des étudiants aux champs
"""
# etudids_sorted: Les etudids des étudiants (diplômés) triés
# champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
# Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.champs_sorted)
for rcstag in self.rcstags.values():
# Charge les moyennes au tag d'un RCStag
if tag in rcstag.moyennes_tags:
moytag = rcstag.moyennes_tags[tag]
notes = moytag.matrice_notes_gen # dataframe etudids x ues
# Etudiants/Champs communs entre le RCSTag et les données interclassées
(
etudids_communs,
champs_communs,
) = pe_comp.find_index_and_columns_communs(df, notes)
# Injecte les notes par tag
df.loc[etudids_communs, champs_communs] = notes.loc[
etudids_communs, champs_communs
]
return df
def compute_coeffs_matrice(self, tag) -> pd.DataFrame:
"""Idem que compute_notes_matrices mais pour les coeffs
Args:
tag: Le tag visé
Return:
Le dataFrame (etudids x champs)
reportant les moyennes des étudiants aux champs
"""
# etudids_sorted: Les etudids des étudiants (diplômés) triés
# champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
# Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.champs_sorted)
for rcstag in self.rcstags.values():
if tag in rcstag.moyennes_tags:
# Charge les coeffs au tag d'un RCStag
coeffs: pd.DataFrame = rcstag.moyennes_tags[tag].matrice_coeffs_moy_gen
# Etudiants/Champs communs entre le RCSTag et les données interclassées
(
etudids_communs,
champs_communs,
) = pe_comp.find_index_and_columns_communs(df, coeffs)
# Injecte les coeffs par tag
df.loc[etudids_communs, champs_communs] = coeffs.loc[
etudids_communs, champs_communs
]
return df
def _do_ues_ou_competences_list(self) -> list[str]:
"""Synthétise les champs (UEs ou compétences) sur lesquels
sont calculés les moyennes.
Returns:
Un dictionnaire {'acronyme_ue' : 'compétences'}
"""
dict_champs = []
for rcstag in self.rcstags.values():
if isinstance(rcstag, pe_sxtag.SxTag):
champs = rcstag.acronymes_sorted
else: # pe_rcstag.RCSTag
champs = rcstag.competences_sorted
dict_champs.extend(champs)
return sorted(set(dict_champs))
def has_tags(self):
"""Indique si l'interclassement a des tags (cas d'un
interclassement sur un S5 qui n'a pas eu lieu)
"""
return len(self.tags_sorted) > 0
def _un_rcstag_significatif(self, rcsstags: dict[(str, int):pe_tabletags]):
"""Renvoie un rcstag significatif (ayant des tags et des notes aux tags)
parmi le dictionnaire de rcsstags"""
for rcstag_id, rcstag in rcsstags.items():
moystags: pe_moytag.MoyennesTag = rcstag.moyennes_tags
for tag, moystag in moystags.items():
tags_tries = moystag.get_all_significant_tags()
if tags_tries:
return moystag
return None
def compute_df_synthese_moyennes_tag(
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())
Args:
etudids_sorted: Les etudids des étudiants (diplômés) triés
champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
Return:
Le dataFrame (etudids x champs)
reportant les moyennes des étudiants aux champs
"""
if aggregat:
assert (
aggregat == self.nom_rcs
), "L'interclassement ciblé ne correspond pas à l'aggrégat visé"
etudids_sorted = sorted(list(self.diplomes_ids))
if not self.rcstags:
return None
# Partant d'un dataframe vierge
initialisation = False
df = pd.DataFrame()
# Pour chaque rcs (suivi) associe la liste des etudids l'ayant suivi
asso_rcs_etudids = {}
for etudid in etudids_sorted:
rcs = self.suivis[etudid]
if rcs:
if rcs.rcs_id not in asso_rcs_etudids:
asso_rcs_etudids[rcs.rcs_id] = []
asso_rcs_etudids[rcs.rcs_id].append(etudid)
for rcs_id, etudids in asso_rcs_etudids.items():
# Charge ses moyennes au RCSTag suivi
rcstag = self.rcstags[rcs_id] # Le SxTag ou RCSTag
# Charge la moyenne
if tag in rcstag.moyennes_tags:
moytag: pd.DataFrame = rcstag.moyennes_tags[tag]
df_moytag = moytag.to_df(
aggregat=aggregat, cohorte="Groupe", options=options
)
# Modif les colonnes au regard du 1er df_moytag significatif lu
if not initialisation:
df = pd.DataFrame(
np.nan, index=etudids_sorted, columns=df_moytag.columns
)
colonnes = list(df_moytag.columns)
for col in colonnes:
if col.endswith("rang"):
df[col] = df[col].astype(str)
initialisation = True
# Injecte les notes des étudiants
df.loc[etudids, :] = df_moytag.loc[etudids, :]
return df