ScoDoc-PE/pe_tagtable.py

339 lines
14 KiB
Python
Raw Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2020 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 notes_table
import datetime
import codecs
class TableTag:
"""
Classe mémorisant les moyennes des étudiants à différents tag et permettant de calculer les rangs et les statistiques :
- nom : Nom représentatif des données de la Table
- inscrlist : Les étudiants inscrits dans le TagTag avec leur information de la forme :
{ etudid : dictionnaire d'info extrait de Scodoc, ...}
- taglist : Liste triée des noms des tags
- resultats : Dictionnaire donnant les notes-moyennes de chaque étudiant par tag et la somme commulée
des coeff utilisées dans le calcul de la moyenne pondérée, sous la forme :
{ tag : { etudid: (note_moy, somme_coeff_norm),
...}
- rangs : Dictionnaire donnant les rang par tag de chaque étudiant de la forme :
{ tag : {etudid: rang, ...} }
- nbinscrits : Nombre d'inscrits dans le semestre (pas de distinction entre les tags)
- statistiques : Dictionnaire donnant les stastitiques (moyenne, min, max) des résultats par tag de la forme :
{ tag : (moy, min, max), ...}
"""
def __init__(self, nom=""):
self.nom = nom
self.inscrlist = []
self.identdict = {}
self.taglist = []
self.resultats = {}
self.rangs = {}
self.statistiques = {}
# *****************************************************************************************************************
# Accesseurs
# *****************************************************************************************************************
# -----------------------------------------------------------------------------------------------------------
def get_moy_from_resultats(self, tag, etudid):
"""Renvoie la moyenne obtenue par un étudiant à un tag donné au regard du format de self.resultats"""
return (
self.resultats[tag][etudid][0]
if tag in self.resultats and etudid in self.resultats[tag]
else None
)
# -----------------------------------------------------------------------------------------------------------
def get_rang_from_resultats(self, tag, etudid):
"""Renvoie le rang à un tag d'un étudiant au regard du format de self.resultats"""
return (
self.rangs[tag][etudid]
if tag in self.resultats and etudid in self.resultats[tag]
else None
)
# -----------------------------------------------------------------------------------------------------------
def get_coeff_from_resultats(self, tag, etudid):
"""Renvoie la somme des coeffs de pondération normalisée utilisés dans le calcul de la moyenne à un tag d'un étudiant
au regard du format de self.resultats.
"""
return (
self.resultats[tag][etudid][1]
if tag in self.resultats and etudid in self.resultats[tag]
else None
)
# -----------------------------------------------------------------------------------------------------------
def get_all_tags(self):
"""Renvoie la liste des tags du semestre triée par ordre alphabétique"""
# return self.taglist
return sorted(self.resultats.keys())
# -----------------------------------------------------------------------------------------------------------
def get_nbinscrits(self):
"""Renvoie le nombre d'inscrits"""
return len(self.inscrlist)
# -----------------------------------------------------------------------------------------------------------
def get_moy_from_stats(self, tag):
""" Renvoie la moyenne des notes calculées pour d'un tag donné"""
return self.statistiques[tag][0] if tag in self.statistiques else None
def get_min_from_stats(self, tag):
""" Renvoie la plus basse des notes calculées pour d'un tag donné"""
return self.statistiques[tag][1] if tag in self.statistiques else None
def get_max_from_stats(self, tag):
""" Renvoie la plus haute des notes calculées pour d'un tag donné"""
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",
)
def get_resultatsEtud(self, tag, etudid):
"""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
# *****************************************************************************************************************
# -----------------------------------------------------------------------------------------------------------
def add_moyennesTag(self, tag, listMoyEtCoeff):
"""
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) ]
"""
# ajout des moyennes au dictionnaire résultat
if listMoyEtCoeff:
self.resultats[tag] = {
etudid: (moyenne, somme_coeffs)
for (moyenne, somme_coeffs, etudid) in listMoyEtCoeff
}
# Calcule les rangs
lesMoyennesTriees = sorted(
listMoyEtCoeff, reverse=True, key=lambda col: col[0]
) # triées
self.rangs[tag] = notes_table.comp_ranks(lesMoyennesTriees) # les rangs
# 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
# *****************************************************************************************************************
def comp_stats_d_un_tag(self, tag):
"""
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-", "-", "-")
if tag not in self.resultats:
return stats
notes = [
self.get_moy_from_resultats(tag, etudid) for etudid in self.resultats[tag]
] # 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:
(moy, coeff) = moyenne_ponderee_terme_a_terme(notes_valides, force=True)
self.statistiques[tag] = (moy, max(notes_valides), min(notes_valides))
# ************************************************************************
# Méthodes dévolues aux affichages -> a revoir
# ************************************************************************
def str_resTag_d_un_etudiant(self, tag, etudid, delim=";"):
"""Renvoie une chaine de caractères (valable pour un csv)
décrivant la moyenne et le rang d'un étudiant, pour un tag donné ;
"""
if tag not in self.get_all_tags() or etudid not in self.resultats[tag]:
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
def str_res_d_un_etudiant(self, etudid, delim=";"):
"""Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique). """
return delim.join(
[self.str_resTag_d_un_etudiant(tag, etudid) for tag in self.get_all_tags()]
)
# -----------------------------------------------------------------------
def str_moytag(cls, moyenne, rang, nbinscrit, delim=";"):
"""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)
# -----------------------------------------------------------------------
def str_tagtable(self, delim=";", decimal_sep=","):
"""Renvoie une chaine de caractère listant toutes les moyennes, les rangs des étudiants pour tous les tags. """
entete = ["etudid", "nom", "prenom"]
for tag in self.get_all_tags():
entete += [titre + "_" + tag for titre in ["note", "rang", "nb_inscrit"]]
chaine = delim.join(entete) + "\n"
for etudid in self.identdict:
descr = delim.join(
[
etudid,
self.identdict[etudid]["nom"],
self.identdict[etudid]["prenom"],
]
)
descr += delim + self.str_res_d_un_etudiant(etudid, delim)
chaine += descr + "\n"
# Ajout des stats ... à faire
if decimal_sep != ".":
return chaine.replace(".", decimal_sep)
else:
return chaine
# ************************************************************************
# Fonctions diverses
# ************************************************************************
# *********************************************
def moyenne_ponderee_terme_a_terme(notes, coeffs=None, force=False):
"""
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.
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).
"""
# Vérification des paramètres d'entrée
if not isinstance(notes, list) or (
coeffs != None and not isinstance(coeffs, list) and len(coeffs) != len(notes)
):
raise ValueError("Erreur de paramètres dans moyenne_ponderee_terme_a_terme")
# Récupération des valeurs des paramètres d'entrée
coeffs = [1] * len(notes) if coeffs == None else coeffs
# S'il n'y a pas de notes
if not notes: # Si notes = []
return (None, None)
notesValides = [
(1 if isinstance(note, float) or isinstance(note, int) else 0) for note in notes
] # Liste indiquant les notes valides
if force == True or (
force == False and sum(notesValides) == len(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
(moyenne, ponderation) = (0.0, 0.0)
for i in range(len(notes)):
if notesValides[i]:
moyenne += coeffs[i] * notes[i]
ponderation += coeffs[i]
return (
(moyenne / (ponderation * 1.0), ponderation)
if ponderation != 0
else (None, 0)
)
else: # Si on ne force pas le calcul de la moyenne
return (None, None)
# -------------------------------------------------------------------------------------------
def conversionDate_StrToDate(date_fin):
""" Conversion d'une date fournie sous la forme d'une chaine de caractère de
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