2020-09-26 16:19:37 +02:00
|
|
|
# -*- mode: python -*-
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# Gestion scolarite IUT
|
|
|
|
#
|
2023-12-31 23:04:06 +01:00
|
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
2020-09-26 16:19:37 +02:00
|
|
|
#
|
|
|
|
# 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 datetime
|
2022-03-17 15:33:33 +01:00
|
|
|
import numpy as np
|
2021-06-19 23:21:37 +02:00
|
|
|
|
2023-02-20 21:04:29 +01:00
|
|
|
from app.scodoc import sco_utils as scu
|
2024-01-20 16:34:38 +01:00
|
|
|
import pandas as pd
|
|
|
|
|
|
|
|
|
|
|
|
TAGS_RESERVES = ["but"]
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
|
2021-07-09 23:31:16 +02:00
|
|
|
class TableTag(object):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""
|
2024-01-20 16:34:38 +01:00
|
|
|
Classe mémorisant les moyennes des étudiants à différents tags et permettant de
|
|
|
|
calculer des rangs et des statistiques.
|
|
|
|
|
|
|
|
Ses attributs sont:
|
|
|
|
|
|
|
|
* nom : Nom représentatif des données de la Table
|
|
|
|
* inscrlist : Les étudiants inscrits dans le TagTag avec leur information de la forme :
|
2020-09-26 16:19:37 +02:00
|
|
|
{ etudid : dictionnaire d'info extrait de Scodoc, ...}
|
2024-01-20 16:34:38 +01:00
|
|
|
* taglist : Liste triée des noms des tags
|
|
|
|
* resultats : Dictionnaire donnant les notes-moyennes de chaque étudiant par tag et la somme commulée
|
2020-09-26 16:19:37 +02:00
|
|
|
des coeff utilisées dans le calcul de la moyenne pondérée, sous la forme :
|
|
|
|
{ tag : { etudid: (note_moy, somme_coeff_norm),
|
|
|
|
...}
|
2024-01-20 16:34:38 +01:00
|
|
|
* rangs : Dictionnaire donnant les rang par tag de chaque étudiant de la forme :
|
2020-09-26 16:19:37 +02:00
|
|
|
{ tag : {etudid: rang, ...} }
|
2024-01-20 16:34:38 +01:00
|
|
|
* nbinscrits : Nombre d'inscrits dans le semestre (pas de distinction entre les tags)
|
|
|
|
* statistiques : Dictionnaire donnant les statistiques (moyenne, min, max) des résultats par tag de la forme :
|
2020-09-26 16:19:37 +02:00
|
|
|
{ tag : (moy, min, max), ...}
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2024-01-20 16:34:38 +01:00
|
|
|
def __init__(self, nom: str):
|
|
|
|
"""Les attributs basiques des TagTable, qui seront initialisés
|
|
|
|
dans les classes dérivées
|
|
|
|
"""
|
2020-09-26 16:19:37 +02:00
|
|
|
self.nom = nom
|
2024-01-20 16:34:38 +01:00
|
|
|
"""Les étudiants"""
|
|
|
|
self.etudiants = {}
|
|
|
|
"""Les moyennes par tag"""
|
|
|
|
self.moyennes_tags = {}
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------------------------------------------
|
|
|
|
def get_all_tags(self):
|
2024-01-21 18:55:21 +01:00
|
|
|
"""Liste des tags de la table, triée par ordre alphabétique
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Liste de tags triés par ordre alphabétique
|
|
|
|
"""
|
2024-01-20 16:34:38 +01:00
|
|
|
return sorted(self.moyennes_tags.keys())
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
|
|
# Accesseurs
|
|
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------------------------------------------
|
2024-01-16 15:51:22 +01:00
|
|
|
def get_moy_from_resultats(self, tag, etudid):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""Renvoie la moyenne obtenue par un étudiant à un tag donné au regard du format de self.resultats"""
|
|
|
|
return (
|
2024-01-20 16:34:38 +01:00
|
|
|
self.moyennes_tags[tag][etudid][0]
|
|
|
|
if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
|
2020-09-26 16:19:37 +02:00
|
|
|
else None
|
|
|
|
)
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------------------------------------------
|
2024-01-16 15:51:22 +01:00
|
|
|
def get_rang_from_resultats(self, tag, etudid):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""Renvoie le rang à un tag d'un étudiant au regard du format de self.resultats"""
|
|
|
|
return (
|
|
|
|
self.rangs[tag][etudid]
|
2024-01-20 16:34:38 +01:00
|
|
|
if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
|
2020-09-26 16:19:37 +02:00
|
|
|
else None
|
|
|
|
)
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------------------------------------------
|
2024-01-16 15:51:22 +01:00
|
|
|
def get_coeff_from_resultats(self, tag, etudid):
|
2021-01-01 18:40:47 +01:00
|
|
|
"""Renvoie la somme des coeffs de pondération normalisée utilisés dans le calcul de la moyenne à un tag d'un étudiant
|
2020-09-26 16:19:37 +02:00
|
|
|
au regard du format de self.resultats.
|
|
|
|
"""
|
|
|
|
return (
|
2024-01-20 16:34:38 +01:00
|
|
|
self.moyennes_tags[tag][etudid][1]
|
|
|
|
if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
|
2020-09-26 16:19:37 +02:00
|
|
|
else None
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------------------------------------------
|
|
|
|
def get_nbinscrits(self):
|
|
|
|
"""Renvoie le nombre d'inscrits"""
|
|
|
|
return len(self.inscrlist)
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------------------------------------------
|
2024-01-16 15:51:22 +01:00
|
|
|
def get_moy_from_stats(self, tag):
|
2021-12-24 00:08:25 +01:00
|
|
|
"""Renvoie la moyenne des notes calculées pour d'un tag donné"""
|
2020-09-26 16:19:37 +02:00
|
|
|
return self.statistiques[tag][0] if tag in self.statistiques else None
|
|
|
|
|
2024-01-16 15:51:22 +01:00
|
|
|
def get_min_from_stats(self, tag):
|
2021-12-24 00:08:25 +01:00
|
|
|
"""Renvoie la plus basse des notes calculées pour d'un tag donné"""
|
2020-09-26 16:19:37 +02:00
|
|
|
return self.statistiques[tag][1] if tag in self.statistiques else None
|
|
|
|
|
2024-01-16 15:51:22 +01:00
|
|
|
def get_max_from_stats(self, tag):
|
2021-12-24 00:08:25 +01:00
|
|
|
"""Renvoie la plus haute des notes calculées pour d'un tag donné"""
|
2020-09-26 16:19:37 +02:00
|
|
|
return self.statistiques[tag][2] if tag in self.statistiques else None
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------------------------------------------
|
|
|
|
# La structure des données mémorisées pour chaque tag dans le dictionnaire de synthèse
|
|
|
|
# d'un jury PE
|
|
|
|
FORMAT_DONNEES_ETUDIANTS = (
|
|
|
|
"note",
|
|
|
|
"coeff",
|
|
|
|
"rang",
|
|
|
|
"nbinscrits",
|
|
|
|
"moy",
|
|
|
|
"max",
|
|
|
|
"min",
|
|
|
|
)
|
|
|
|
|
2024-01-16 15:51:22 +01:00
|
|
|
def get_resultatsEtud(self, tag, etudid):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""Renvoie un tuple (note, coeff, rang, nb_inscrit, moy, min, max) synthétisant les résultats d'un étudiant
|
|
|
|
à un tag donné. None sinon"""
|
|
|
|
return (
|
|
|
|
self.get_moy_from_resultats(tag, etudid),
|
|
|
|
self.get_coeff_from_resultats(tag, etudid),
|
|
|
|
self.get_rang_from_resultats(tag, etudid),
|
|
|
|
self.get_nbinscrits(),
|
|
|
|
self.get_moy_from_stats(tag),
|
|
|
|
self.get_min_from_stats(tag),
|
|
|
|
self.get_max_from_stats(tag),
|
|
|
|
)
|
|
|
|
|
|
|
|
# return self.tag_stats[tag]
|
|
|
|
# else :
|
|
|
|
# return self.pe_stats
|
|
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
|
|
# Ajout des notes
|
|
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------------------------------------------
|
2024-01-16 15:51:22 +01:00
|
|
|
def add_moyennesTag(self, tag, listMoyEtCoeff) -> bool:
|
2020-09-26 16:19:37 +02:00
|
|
|
"""
|
|
|
|
Mémorise les moyennes, les coeffs de pondération et les etudid dans resultats
|
|
|
|
avec calcul du rang
|
|
|
|
:param tag: Un tag
|
|
|
|
:param listMoyEtCoeff: Une liste donnant [ (moy, coeff, etudid) ]
|
2024-01-20 16:34:38 +01:00
|
|
|
|
|
|
|
TODO:: Inutile maintenant ?
|
2020-09-26 16:19:37 +02:00
|
|
|
"""
|
|
|
|
# ajout des moyennes au dictionnaire résultat
|
|
|
|
if listMoyEtCoeff:
|
2024-01-20 16:34:38 +01:00
|
|
|
self.moyennes_tags[tag] = {
|
2020-09-26 16:19:37 +02:00
|
|
|
etudid: (moyenne, somme_coeffs)
|
|
|
|
for (moyenne, somme_coeffs, etudid) in listMoyEtCoeff
|
|
|
|
}
|
|
|
|
|
|
|
|
# Calcule les rangs
|
|
|
|
lesMoyennesTriees = sorted(
|
2021-08-29 23:04:38 +02:00
|
|
|
listMoyEtCoeff,
|
|
|
|
reverse=True,
|
2021-09-08 00:11:11 +02:00
|
|
|
key=lambda col: col[0]
|
|
|
|
if isinstance(col[0], float)
|
|
|
|
else 0, # remplace les None et autres chaines par des zéros
|
2020-09-26 16:19:37 +02:00
|
|
|
) # triées
|
2023-02-20 21:04:29 +01:00
|
|
|
self.rangs[tag] = scu.comp_ranks(lesMoyennesTriees) # les rangs
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
# calcul des stats
|
|
|
|
self.comp_stats_d_un_tag(tag)
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
|
|
# Méthodes dévolues aux calculs de statistiques (min, max, moy) sur chaque moyenne taguée
|
|
|
|
# *****************************************************************************************************************
|
|
|
|
|
2024-01-16 15:51:22 +01:00
|
|
|
def comp_stats_d_un_tag(self, tag):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""
|
|
|
|
Calcule la moyenne generale, le min, le max pour un tag donné,
|
|
|
|
en ne prenant en compte que les moyennes significatives. Mémorise le resultat dans
|
|
|
|
self.statistiques
|
|
|
|
"""
|
|
|
|
stats = ("-NA-", "-", "-")
|
2024-01-20 16:34:38 +01:00
|
|
|
if tag not in self.moyennes_tags:
|
2020-09-26 16:19:37 +02:00
|
|
|
return stats
|
|
|
|
|
|
|
|
notes = [
|
2024-01-20 16:34:38 +01:00
|
|
|
self.get_moy_from_resultats(tag, etudid)
|
|
|
|
for etudid in self.moyennes_tags[tag]
|
2020-09-26 16:19:37 +02:00
|
|
|
] # les notes du tag
|
|
|
|
notes_valides = [
|
|
|
|
note for note in notes if isinstance(note, float) and note != None
|
|
|
|
]
|
|
|
|
nb_notes_valides = len(notes_valides)
|
|
|
|
if nb_notes_valides > 0:
|
2021-02-02 14:49:49 +01:00
|
|
|
(moy, _) = moyenne_ponderee_terme_a_terme(notes_valides, force=True)
|
2020-09-26 16:19:37 +02:00
|
|
|
self.statistiques[tag] = (moy, max(notes_valides), min(notes_valides))
|
|
|
|
|
|
|
|
# ************************************************************************
|
|
|
|
# Méthodes dévolues aux affichages -> a revoir
|
|
|
|
# ************************************************************************
|
2024-01-16 15:51:22 +01:00
|
|
|
def str_resTag_d_un_etudiant(self, tag, etudid, delim=";"):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""Renvoie une chaine de caractères (valable pour un csv)
|
|
|
|
décrivant la moyenne et le rang d'un étudiant, pour un tag donné ;
|
|
|
|
"""
|
2024-01-20 16:34:38 +01:00
|
|
|
if tag not in self.get_all_tags() or etudid not in self.moyennes_tags[tag]:
|
2020-09-26 16:19:37 +02:00
|
|
|
return ""
|
|
|
|
|
|
|
|
moystr = TableTag.str_moytag(
|
|
|
|
self.get_moy_from_resultats(tag, etudid),
|
|
|
|
self.get_rang_from_resultats(tag, etudid),
|
|
|
|
self.get_nbinscrits(),
|
|
|
|
delim=delim,
|
|
|
|
)
|
|
|
|
return moystr
|
|
|
|
|
2024-01-16 15:51:22 +01:00
|
|
|
def str_res_d_un_etudiant(self, etudid, delim=";"):
|
2021-12-24 00:08:25 +01:00
|
|
|
"""Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique)."""
|
2020-09-26 16:19:37 +02:00
|
|
|
return delim.join(
|
|
|
|
[self.str_resTag_d_un_etudiant(tag, etudid) for tag in self.get_all_tags()]
|
|
|
|
)
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
2024-01-16 15:51:22 +01:00
|
|
|
def str_moytag(cls, moyenne, rang, nbinscrit, delim=";"):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""Renvoie une chaine de caractères représentant une moyenne (float ou string) et un rang
|
|
|
|
pour différents formats d'affichage : HTML, debug ligne de commande, csv"""
|
|
|
|
moystr = (
|
|
|
|
"%2.2f%s%s%s%d" % (moyenne, delim, rang, delim, nbinscrit)
|
|
|
|
if isinstance(moyenne, float)
|
|
|
|
else str(moyenne) + delim + str(rang) + delim + str(nbinscrit)
|
|
|
|
)
|
|
|
|
return moystr
|
|
|
|
|
|
|
|
str_moytag = classmethod(str_moytag)
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
|
2024-01-21 18:55:21 +01:00
|
|
|
def df_tagtable(self):
|
|
|
|
"""Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Un dataframe etudids x tag (avec tag par ordre alphabétique)
|
|
|
|
"""
|
|
|
|
tags = self.get_all_tags()
|
|
|
|
if tags:
|
|
|
|
dict_series = {tag: self.moyennes_tags[tag]["notes"] for tag in tags}
|
|
|
|
df = pd.DataFrame(dict_series)
|
|
|
|
return df
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2024-01-20 16:34:38 +01:00
|
|
|
def str_tagtable(self):
|
|
|
|
"""Renvoie une chaine de caractère listant toutes les moyennes,
|
|
|
|
les rangs des étudiants pour tous les tags."""
|
|
|
|
|
|
|
|
etudiants = self.etudiants
|
|
|
|
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
|
|
|
|
|
2020-09-26 16:19:37 +02:00
|
|
|
for tag in self.get_all_tags():
|
2024-01-20 16:34:38 +01:00
|
|
|
df = df.join(self.moyennes_tags[tag]["notes"].rename(f"moy {tag}"))
|
|
|
|
df = df.join(self.moyennes_tags[tag]["classements"].rename(f"class {tag}"))
|
|
|
|
|
|
|
|
return df.to_csv(sep=";")
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ************************************************************************
|
|
|
|
# Fonctions diverses
|
|
|
|
# ************************************************************************
|
|
|
|
|
|
|
|
|
|
|
|
# *********************************************
|
2024-01-16 15:51:22 +01:00
|
|
|
def moyenne_ponderee_terme_a_terme(notes, coefs=None, force=False):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""
|
|
|
|
Calcule la moyenne pondérée d'une liste de notes avec d'éventuels coeffs de pondération.
|
|
|
|
Renvoie le résultat sous forme d'un tuple (moy, somme_coeff)
|
|
|
|
|
2022-03-17 15:33:33 +01:00
|
|
|
La liste de notes contient soit :
|
|
|
|
1) des valeurs numériques
|
|
|
|
2) des strings "-NA-" (pas de notes) ou "-NI-" (pas inscrit) ou "-c-" ue capitalisée,
|
|
|
|
3) None.
|
|
|
|
|
2020-09-26 16:19:37 +02:00
|
|
|
Le paramètre force indique si le calcul de la moyenne doit être forcée ou non, c'est à
|
2022-03-17 15:33:33 +01:00
|
|
|
dire s'il y a ou non omission des notes non numériques (auquel cas la moyenne est
|
|
|
|
calculée sur les notes disponibles) ; sinon renvoie (None, None).
|
2020-09-26 16:19:37 +02:00
|
|
|
"""
|
|
|
|
# Vérification des paramètres d'entrée
|
|
|
|
if not isinstance(notes, list) or (
|
2022-03-17 15:33:33 +01:00
|
|
|
coefs != None and not isinstance(coefs, list) and len(coefs) != len(notes)
|
2020-09-26 16:19:37 +02:00
|
|
|
):
|
|
|
|
raise ValueError("Erreur de paramètres dans moyenne_ponderee_terme_a_terme")
|
|
|
|
|
|
|
|
# Récupération des valeurs des paramètres d'entrée
|
2022-03-17 15:33:33 +01:00
|
|
|
coefs = [1] * len(notes) if coefs is None else coefs
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
# S'il n'y a pas de notes
|
|
|
|
if not notes: # Si notes = []
|
|
|
|
return (None, None)
|
|
|
|
|
2022-03-17 15:33:33 +01:00
|
|
|
# Liste indiquant les notes valides
|
|
|
|
notes_valides = [
|
|
|
|
(isinstance(note, float) and not np.isnan(note)) or isinstance(note, int)
|
|
|
|
for note in notes
|
|
|
|
]
|
|
|
|
# Si on force le calcul de la moyenne ou qu'on ne le force pas
|
|
|
|
# et qu'on a le bon nombre de notes
|
|
|
|
if force or sum(notes_valides) == len(notes):
|
|
|
|
moyenne, ponderation = 0.0, 0.0
|
2020-09-26 16:19:37 +02:00
|
|
|
for i in range(len(notes)):
|
2022-03-17 15:33:33 +01:00
|
|
|
if notes_valides[i]:
|
|
|
|
moyenne += coefs[i] * notes[i]
|
|
|
|
ponderation += coefs[i]
|
2020-09-26 16:19:37 +02:00
|
|
|
return (
|
|
|
|
(moyenne / (ponderation * 1.0), ponderation)
|
|
|
|
if ponderation != 0
|
|
|
|
else (None, 0)
|
|
|
|
)
|
2022-03-17 15:33:33 +01:00
|
|
|
# Si on ne force pas le calcul de la moyenne
|
|
|
|
return (None, None)
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
# -------------------------------------------------------------------------------------------
|
|
|
|
def conversionDate_StrToDate(date_fin):
|
2021-01-01 18:40:47 +01:00
|
|
|
"""Conversion d'une date fournie sous la forme d'une chaine de caractère de
|
2020-09-26 16:19:37 +02:00
|
|
|
type 'jj/mm/aaaa' en un objet date du package datetime.
|
|
|
|
Fonction servant au tri des semestres par date
|
|
|
|
"""
|
|
|
|
(d, m, y) = [int(x) for x in date_fin.split("/")]
|
|
|
|
date_fin_dst = datetime.date(y, m, d)
|
|
|
|
return date_fin_dst
|