ScoDoc/app/pe/pe_tabletags.py

368 lines
15 KiB
Python
Raw Normal View History

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
import numpy as np
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(
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)
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 à
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 (
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
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)
# 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)):
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)
)
# 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