342 lines
15 KiB
Python
342 lines
15 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
|
|
"""
|
|
from app.comp.res_sem import load_formsemestre_results
|
|
from app.models import FormSemestre
|
|
from app.pe.pe_semestretag import SemestreTag
|
|
from app.pe.pe_tools import pe_print, PE_DEBUG
|
|
from app.pe import pe_tagtable
|
|
import pandas as pd
|
|
import numpy as np
|
|
|
|
|
|
from app.pe.pe_etudiant import EtudiantsJuryPE
|
|
|
|
|
|
class SetTag(pe_tagtable.TableTag):
|
|
"""Agrège plusieurs semestres (ou settag) taggués (SemestreTag/Settag de 1 à 4) pour extraire des moyennes
|
|
et des classements par tag pour un groupe d'étudiants donnés.
|
|
par. exemple fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S'
|
|
Le settag est identifié sur la base du dernier semestre (ici le 'S3') ;
|
|
les étudiants considérés sont donc ceux inscrits dans ce S3
|
|
à condition qu'ils disposent d'un parcours sur tous les semestres fusionnés valides (par. ex
|
|
un etudiant non inscrit dans un S1 mais dans un S2 et un S3 n'est pas pris en compte).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
nom,
|
|
formsemestre_terminal: FormSemestre,
|
|
semestres_aggreges: dict[int, FormSemestre],
|
|
semestres_taggues: dict[int, SemestreTag],
|
|
donnees_etudiants: EtudiantsJuryPE,
|
|
):
|
|
pe_tagtable.TableTag.__init__(self, nom)
|
|
|
|
"""Le formsemestre terminal et les semestres aggrégés"""
|
|
self.formsemestre_terminal = formsemestre_terminal
|
|
nt = load_formsemestre_results(formsemestre_terminal)
|
|
self.semestres_aggreges = semestres_aggreges
|
|
|
|
"""Les semestres tags associés aux semestres aggrégés"""
|
|
try:
|
|
self.semestres_tags_aggreges = {
|
|
frmsem_id: semestres_taggues[frmsem_id]
|
|
for frmsem_id in semestres_taggues
|
|
}
|
|
except:
|
|
raise ValueError("Semestres taggués manquants")
|
|
|
|
"""Les étudiants (état civil + cursus connu)"""
|
|
self.etuds = nt.etuds
|
|
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
|
|
self.cursus = {
|
|
etudid: donnees_etudiants.cursus[etudid] for etudid in self.etudiants
|
|
}
|
|
|
|
"""Les tags extraits de tous les semestres"""
|
|
self.tags_sorted = self.do_taglist()
|
|
|
|
"""Construit le cube de notes"""
|
|
self.notes_cube = {}
|
|
|
|
"""Les moyennes par tag"""
|
|
self.moyennes_tags = {}
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def compute_notes_cube(self):
|
|
"""Construit le cube de notes (etudid x tags x semestre_aggregé)
|
|
nécessaire au calcul des moyennes de l'aggrégat
|
|
"""
|
|
nb_tags = len(self.tags_sorted)
|
|
nb_etudiants = len(self.etuds)
|
|
nb_semestres = len(self.semestres_tags_aggreges)
|
|
|
|
"""Index du cube (etudids -> dim 0, tags -> dim 1)"""
|
|
etudids = [etud.etudid for etud in self.etuds]
|
|
tags = self.tags_sorted
|
|
semestres_id = list(self.semestres_tags_aggreges.keys())
|
|
|
|
dfs = {}
|
|
|
|
for frmsem_id in semestres_id:
|
|
"""Partant d'un dataframe vierge"""
|
|
df = pd.DataFrame(np.nan, index=etudids, columns=self.tags_sorted)
|
|
|
|
"""Charge les notes du semestre tag"""
|
|
notes = self.semestres_tags_aggreges[frmsem_id].notes
|
|
|
|
"""Les étudiants & les tags commun au dataframe final et aux notes du semestre)"""
|
|
etudids_communs = df.index.intersection(notes.index)
|
|
tags_communs = df.columns.intersection(notes.columns)
|
|
|
|
"""Injecte les notes par tag"""
|
|
df.loc[etudids_communs, tags_communs] = notes.loc[
|
|
etudids_communs, tags_communs
|
|
]
|
|
|
|
"""Stocke le df"""
|
|
dfs[frmsem_id] = df
|
|
|
|
"""Réunit les notes sous forme d'un cube etdids x tags x semestres"""
|
|
semestres_x_etudids_x_tags = [dfs[fid].values for fid in dfs]
|
|
etudids_x_tags_x_semestres = np.stack(semestres_x_etudids_x_tags, axis=-1)
|
|
|
|
return etudids_x_tags_x_semestres
|
|
|
|
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def get_etudids(self):
|
|
return list(self.identdict.keys())
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def do_taglist(self):
|
|
"""Synthétise les tags à partir des semestres (taggués) aggrégés
|
|
|
|
Returns:
|
|
Une liste de tags triés par ordre alphabétique
|
|
"""
|
|
tags = []
|
|
for frmsem_id in self.semestres_tags_aggreges:
|
|
tags.extend(self.semestres_tags_aggreges[frmsem_id].tags_sorted)
|
|
return sorted(set(tags))
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def do_tagdict(self):
|
|
"""Synthétise la liste des modules pris en compte dans le calcul d'un tag (pour analyse des résultats)"""
|
|
self.tagdict = {}
|
|
for semtag in self.SemTagDict.values():
|
|
for tag in semtag.get_all_tags():
|
|
if tag != "but":
|
|
if tag not in self.tagdict:
|
|
self.tagdict[tag] = {}
|
|
for mod in semtag.tagdict[tag]:
|
|
self.tagdict[tag][mod] = semtag.tagdict[tag][mod]
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def get_NotesEtCoeffsSetTagEtudiant(self, tag, etudid):
|
|
"""Récupère tous les notes et les coeffs d'un étudiant relatives à un tag dans ses semestres valides et les renvoie dans un tuple (notes, coeffs)
|
|
avec notes et coeffs deux listes"""
|
|
lesSemsDeLEtudiant = [
|
|
self.parcoursDict[etudid][nom_sem] for nom_sem in self.parcours
|
|
] # peuvent être None
|
|
|
|
notes = [
|
|
self.SemTagDict[fid].get_moy_from_resultats(tag, etudid)
|
|
for fid in lesSemsDeLEtudiant
|
|
if tag in self.SemTagDict[fid].taglist
|
|
] # eventuellement None
|
|
coeffs = [
|
|
self.SemTagDict[fid].get_coeff_from_resultats(tag, etudid)
|
|
for fid in lesSemsDeLEtudiant
|
|
if tag in self.SemTagDict[fid].taglist
|
|
]
|
|
return (notes, coeffs)
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def comp_MoyennesSetTag(self, tag, force=False):
|
|
"""Calcule et renvoie les "moyennes" des étudiants à un tag donné, en prenant en compte tous les semestres taggués
|
|
de l'aggrégat, et leur coeff Par moyenne, s'entend une note moyenne, la somme des coefficients de pondération
|
|
appliqué dans cette moyenne.
|
|
|
|
Force ou non le calcul de la moyenne lorsque des notes sont manquantes.
|
|
|
|
Renvoie les informations sous la forme d'une liste [etudid: (moy, somme_coeff_normalisée, rang), ...}
|
|
"""
|
|
# if tag not in self.get_all_tags() : return None
|
|
|
|
# Calcule les moyennes
|
|
lesMoyennes = []
|
|
for (
|
|
etudid
|
|
) in (
|
|
self.get_etudids()
|
|
): # Pour tous les étudiants non défaillants du semestre inscrits dans des modules relatifs au tag
|
|
(notes, coeffs_norm) = self.get_NotesEtCoeffsSetTagEtudiant(
|
|
tag, etudid
|
|
) # lecture des notes associées au tag
|
|
(moyenne, somme_coeffs) = pe_tagtable.moyenne_ponderee_terme_a_terme(
|
|
notes, coeffs_norm, force=force
|
|
)
|
|
lesMoyennes += [
|
|
(moyenne, somme_coeffs, etudid)
|
|
] # Un tuple (pour classement résumant les données)
|
|
return lesMoyennes
|
|
|
|
|
|
class SetTagInterClasse(pe_tagtable.TableTag):
|
|
"""Récupère les moyennes de SetTag aggrégeant un même parcours (par ex un ['S1', 'S2'] n'ayant pas fini au même S2
|
|
pour fournir un interclassement sur un groupe d'étudiant => seul compte alors la promo
|
|
nom_combinaison = 'S1' ou '1A'
|
|
"""
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def __init__(self, nom_combinaison, diplome):
|
|
pe_tagtable.TableTag.__init__(self, nom=f"{nom_combinaison}_{diplome or ''}")
|
|
self.combinaison = nom_combinaison
|
|
self.parcoursDict = {}
|
|
|
|
# -------------------------------------------------------------------------------------------
|
|
def set_Etudiants(self, etudiants, juryPEDict, etudInfoDict, nom_sem_final=None):
|
|
"""Détermine la liste des étudiants à prendre en compte, en partant de
|
|
la liste fournie en paramètre et en vérifiant que l'étudiant dispose bien d'un parcours valide pour la combinaison demandée.
|
|
Renvoie le nombre d'étudiants effectivement inscrits."""
|
|
if nom_sem_final:
|
|
self.nom += "_" + nom_sem_final
|
|
for etudid in etudiants:
|
|
if juryPEDict[etudid][self.combinaison] != None:
|
|
self.inscrlist.append(etudInfoDict[etudid])
|
|
self.identdict[etudid] = etudInfoDict[etudid]
|
|
self.parcoursDict[etudid] = juryPEDict[etudid]
|
|
return len(self.inscrlist)
|
|
|
|
# -------------------------------------------------------------------------------------------
|
|
def get_Fids_in_settag(self):
|
|
"""Renvoie la liste des semestres (les formsemestre_id finissant la combinaison par ex. '3S' dont les fid des S3) à prendre en compte
|
|
pour les moyennes, en considérant tous les étudiants inscrits"""
|
|
return list(
|
|
{self.parcoursDict[etudid][self.combinaison] for etudid in self.identdict}
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
def set_SetTagDict(self, SetTagDict):
|
|
"""Mémorise les settag nécessaires au jury."""
|
|
self.SetTagDict = {
|
|
fid: SetTagDict[fid] for fid in self.get_Fids_in_settag() if fid != None
|
|
}
|
|
if PE_DEBUG >= 1:
|
|
pe_print(" => %d semestres utilisés" % len(self.SetTagDict))
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def comp_data_settag(self):
|
|
"""Calcule tous les données numériques relatives au settag"""
|
|
# Attributs relatifs aux tag pour les modules pris en compte
|
|
self.taglist = self.do_taglist()
|
|
|
|
# if PE_DEBUG >= 1: pe_print(" => Tags = " + ", ".join( self.taglist ))
|
|
|
|
# Calcul des moyennes de chaque étudiant par tag
|
|
reussiteAjoutTag = {"OK": [], "KO": []}
|
|
for tag in self.taglist:
|
|
moyennes = self.get_MoyennesSetTag(tag, force=False)
|
|
res = self.add_moyennesTag(tag, moyennes) # pas de notes => pas de moyenne
|
|
reussiteAjoutTag["OK" if res else "KO"].append(tag)
|
|
if len(reussiteAjoutTag["OK"]) > 0 and PE_DEBUG:
|
|
pe_print(
|
|
" => Interclassement de %d tags : " % (len(reussiteAjoutTag["OK"]))
|
|
+ ", ".join(reussiteAjoutTag["OK"])
|
|
)
|
|
if len(reussiteAjoutTag["KO"]) > 0 and PE_DEBUG:
|
|
pe_print(
|
|
" => %d tags manquants : " % (len(reussiteAjoutTag["KO"]))
|
|
+ ", ".join(reussiteAjoutTag["KO"])
|
|
)
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def get_etudids(self):
|
|
return list(self.identdict.keys())
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def do_taglist(self):
|
|
"""Parcourt les tags des semestres taggués et les synthétise sous la forme
|
|
d'une liste en supprimant les doublons
|
|
"""
|
|
ensemble = []
|
|
for settag in self.SetTagDict.values():
|
|
ensemble.extend(settag.get_all_tags())
|
|
return sorted(list(set(ensemble)))
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def get_NotesEtCoeffsSetTagEtudiant(self, tag, etudid):
|
|
"""Récupère tous les notes et les coeffs d'un étudiant relatives à un tag dans ses semestres valides et les renvoie dans un tuple (notes, coeffs)
|
|
avec notes et coeffs deux listes"""
|
|
leSetTagDeLetudiant = self.parcoursDict[etudid][self.combinaison]
|
|
|
|
note = self.SetTagDict[leSetTagDeLetudiant].get_moy_from_resultats(tag, etudid)
|
|
coeff = self.SetTagDict[leSetTagDeLetudiant].get_coeff_from_resultats(
|
|
tag, etudid
|
|
)
|
|
return (note, coeff)
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------
|
|
def get_MoyennesSetTag(self, tag, force=False):
|
|
"""Renvoie les "moyennes" des étudiants à un tag donné, en prenant en compte tous les settag de l'aggrégat,
|
|
et leur coeff Par moyenne, s'entend une note moyenne, la somme des coefficients de pondération
|
|
appliqué dans cette moyenne.
|
|
|
|
Force ou non le calcul de la moyenne lorsque des notes sont manquantes.
|
|
|
|
Renvoie les informations sous la forme d'une liste [etudid: (moy, somme_coeff_normalisée, rang), ...}
|
|
"""
|
|
# if tag not in self.get_all_tags() : return None
|
|
|
|
# Calcule les moyennes
|
|
lesMoyennes = []
|
|
for (
|
|
etudid
|
|
) in (
|
|
self.get_etudids()
|
|
): # Pour tous les étudiants non défaillants du semestre inscrits dans des modules relatifs au tag
|
|
(moyenne, somme_coeffs) = self.get_NotesEtCoeffsSetTagEtudiant(
|
|
tag, etudid
|
|
) # lecture des notes associées au tag
|
|
lesMoyennes += [
|
|
(moyenne, somme_coeffs, etudid)
|
|
] # Un tuple (pour classement résumant les données)
|
|
return lesMoyennes
|