489 lines
20 KiB
Python
489 lines
20 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 import db, log
|
|
from app.comp import res_sem, moy_ue, moy_sem
|
|
from app.comp.res_compat import NotesTableCompat
|
|
from app.comp.res_sem import load_formsemestre_results
|
|
from app.models import FormSemestre
|
|
from app.models.moduleimpls import ModuleImpl
|
|
|
|
from app.scodoc import sco_tag_module
|
|
from app.scodoc.codes_cursus import UE_SPORT
|
|
import app.pe.pe_comp as pe_comp
|
|
from app.pe.pe_tabletags import (TableTag, TAGS_RESERVES)
|
|
|
|
class SemestreTag(TableTag):
|
|
"""Un SemestreTag représente les résultats des étudiants à un semestre, en donnant
|
|
accès aux moyennes par tag.
|
|
Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT.
|
|
"""
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Fonctions d'initialisation
|
|
# -----------------------------------------------------------------------------
|
|
def __init__(self, nom: str, formsemestre_id: int):
|
|
"""
|
|
Args:
|
|
nom: Nom à donner au SemestreTag
|
|
formsemestre_id: Identifiant du FormSemestre sur lequel il se base
|
|
"""
|
|
TableTag.__init__(self, nom=nom)
|
|
|
|
"""Le semestre"""
|
|
self.formsemestre_id = formsemestre_id
|
|
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
|
|
"""Les résultats du semestre"""
|
|
self.nt = load_formsemestre_results(self.formsemestre)
|
|
|
|
"""Les étudiants"""
|
|
self.etuds = self.nt.etuds
|
|
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
|
|
|
|
"""Les notes, les modules implémentés triés, les étudiants, les coeffs,
|
|
récupérés notamment de py:mod:`res_but`
|
|
"""
|
|
self.sem_cube = self.nt.sem_cube
|
|
self.modimpls_sorted = self.nt.formsemestre.modimpls_sorted
|
|
self.modimpl_coefs_df = self.nt.modimpl_coefs_df
|
|
|
|
"""Les inscriptions au module et les dispenses d'UE"""
|
|
self.modimpl_inscr_df = self.nt.modimpl_inscr_df
|
|
self.ues = self.nt.ues
|
|
self.ues_inscr_parcours_df = self.nt.load_ues_inscr_parcours()
|
|
self.dispense_ues = self.nt.dispense_ues
|
|
|
|
"""Les tags (en supprimant les tags réservés)"""
|
|
self.tags = get_synthese_tags_semestre(self.nt.formsemestre)
|
|
for tag in TAGS_RESERVES:
|
|
if tag in self.tags:
|
|
del self.tags[tag]
|
|
|
|
"""Calcul des moyennes & les classements de chaque étudiant à chaque tag"""
|
|
self.moyennes_tags = {}
|
|
|
|
for tag in self.tags:
|
|
pe_comp.pe_print(f" -> Traitement du tag {tag}")
|
|
moy_gen_tag = self.compute_moyenne_tag(tag)
|
|
class_gen_tag = moy_sem.comp_ranks_series(moy_gen_tag)[1] # en int
|
|
self.moyennes_tags[tag] = {
|
|
"notes": moy_gen_tag,
|
|
"classements": class_gen_tag,
|
|
"min": moy_gen_tag.min(),
|
|
"max": moy_gen_tag.max(),
|
|
"moy": moy_gen_tag.mean(),
|
|
"nb_inscrits": len(moy_gen_tag),
|
|
}
|
|
|
|
"""Ajoute les moyennes générales de BUT pour le semestre considéré"""
|
|
pe_comp.pe_print(f" -> Traitement du tag but")
|
|
moy_gen_but = self.nt.etud_moy_gen
|
|
class_gen_but = self.nt.etud_moy_gen_ranks_int
|
|
self.moyennes_tags["but"] = {
|
|
"notes": moy_gen_but,
|
|
"classements": class_gen_but,
|
|
"min": moy_gen_but.min(),
|
|
"max": moy_gen_but.max(),
|
|
"moy": moy_gen_but.mean(),
|
|
"nb_inscrits": len(moy_gen_but),
|
|
}
|
|
|
|
"""Synthétise l'ensemble des moyennes dans un dataframe"""
|
|
self.tags_sorted = sorted(self.moyennes_tags) # les tags par ordre alphabétique
|
|
self.notes = self.df_tagtable() # Le dataframe synthétique des notes (=moyennes par tag)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
def get_etudids(self):
|
|
"""Renvoie la liste des etud_id des étudiants inscrits au semestre"""
|
|
return [etud["etudid"] for etud in self.inscrlist]
|
|
|
|
# -----------------------------------------------------------------------------
|
|
def compute_moyenne_tag(self, tag: str) -> list:
|
|
"""Calcule la moyenne des étudiants pour le tag indiqué,
|
|
pour ce SemestreTag.
|
|
|
|
Sont pris en compte les modules implémentés associés au tag,
|
|
avec leur éventuel coefficient de **repondération**, en utilisant les notes
|
|
chargées pour ce SemestreTag.
|
|
|
|
Force ou non le calcul de la moyenne lorsque des notes sont manquantes.
|
|
|
|
Renvoie les informations sous la forme d'une liste
|
|
[ (moy, somme_coeff_normalise, etudid), ...]
|
|
"""
|
|
|
|
"""Adaptation du mask de calcul des moyennes au tag visé"""
|
|
modimpls_mask = [
|
|
modimpl.module.ue.type != UE_SPORT
|
|
for modimpl in self.formsemestre.modimpls_sorted
|
|
]
|
|
|
|
"""Désactive tous les modules qui ne sont pas pris en compte pour ce tag"""
|
|
for i, modimpl in enumerate(self.formsemestre.modimpls_sorted):
|
|
if modimpl.moduleimpl_id not in self.tags[tag]:
|
|
modimpls_mask[i] = False
|
|
|
|
"""Applique la pondération des coefficients"""
|
|
modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy()
|
|
for modimpl_id in self.tags[tag]:
|
|
ponderation = self.tags[tag][modimpl_id]["ponderation"]
|
|
modimpl_coefs_ponderes_df[modimpl_id] *= ponderation
|
|
|
|
"""Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)"""
|
|
moyennes_ues_tag = moy_ue.compute_ue_moys_apc(
|
|
self.sem_cube,
|
|
self.etuds,
|
|
self.formsemestre.modimpls_sorted,
|
|
self.modimpl_inscr_df,
|
|
modimpl_coefs_ponderes_df,
|
|
modimpls_mask,
|
|
self.dispense_ues,
|
|
block=self.formsemestre.block_moyennes,
|
|
)
|
|
|
|
"""Les ects"""
|
|
ects = self.ues_inscr_parcours_df.fillna(0.0) * [
|
|
ue.ects for ue in self.ues if ue.type != UE_SPORT
|
|
]
|
|
|
|
"""Calcule la moyenne générale dans le semestre (pondérée par le ECTS)"""
|
|
moy_gen_tag = moy_sem.compute_sem_moys_apc_using_ects(
|
|
moyennes_ues_tag,
|
|
ects,
|
|
formation_id=self.formsemestre.formation_id,
|
|
skip_empty_ues=True,
|
|
)
|
|
|
|
return moy_gen_tag
|
|
|
|
# -----------------------------------------------------------------------------
|
|
def get_noteEtCoeff_modimpl(self, modimpl_id, etudid, profondeur=2):
|
|
"""Renvoie un couple donnant la note et le coeff normalisé d'un étudiant à un module d'id modimpl_id.
|
|
La note et le coeff sont extraits :
|
|
1) soit des données du semestre en normalisant le coefficient par rapport à la somme des coefficients des modules du semestre,
|
|
2) soit des données des UE précédemment capitalisées, en recherchant un module de même CODE que le modimpl_id proposé,
|
|
le coefficient normalisé l'étant alors par rapport au total des coefficients du semestre auquel appartient l'ue capitalisée
|
|
"""
|
|
(note, coeff_norm) = (None, None)
|
|
|
|
modimpl = get_moduleimpl(modimpl_id) # Le module considéré
|
|
if modimpl == None or profondeur < 0:
|
|
return (None, None)
|
|
|
|
# Y-a-t-il eu capitalisation d'UE ?
|
|
ue_capitalisees = self.get_ue_capitalisees(
|
|
etudid
|
|
) # les ue capitalisées des étudiants
|
|
ue_capitalisees_id = {
|
|
ue_cap["ue_id"] for ue_cap in ue_capitalisees
|
|
} # les id des ue capitalisées
|
|
|
|
# Si le module ne fait pas partie des UE capitalisées
|
|
if modimpl.module.ue.id not in ue_capitalisees_id:
|
|
note = self.nt.get_etud_mod_moy(modimpl_id, etudid) # lecture de la note
|
|
coeff = modimpl.module.coefficient or 0.0 # le coeff (! non compatible BUT)
|
|
coeff_norm = (
|
|
coeff / self.somme_coeffs if self.somme_coeffs != 0 else 0
|
|
) # le coeff normalisé
|
|
|
|
# Si le module fait partie d'une UE capitalisée
|
|
elif len(ue_capitalisees) > 0:
|
|
moy_ue_actuelle = get_moy_ue_from_nt(
|
|
self.nt, etudid, modimpl_id
|
|
) # la moyenne actuelle
|
|
# A quel semestre correspond l'ue capitalisée et quelles sont ses notes ?
|
|
fids_prec = [
|
|
ue_cap["formsemestre_id"]
|
|
for ue_cap in ue_capitalisees
|
|
if ue_cap["ue_code"] == modimpl.module.ue.ue_code
|
|
] # and ue['semestre_id'] == semestre_id]
|
|
if len(fids_prec) > 0:
|
|
# => le formsemestre_id du semestre dont vient la capitalisation
|
|
fid_prec = fids_prec[0]
|
|
# Lecture des notes de ce semestre
|
|
# le tableau de note du semestre considéré:
|
|
formsemestre_prec = FormSemestre.get_formsemestre(fid_prec)
|
|
nt_prec: NotesTableCompat = res_sem.load_formsemestre_results(
|
|
formsemestre_prec
|
|
)
|
|
|
|
# Y-a-t-il un module équivalent c'est à dire correspondant au même code (le module_id n'étant pas significatif en cas de changement de PPN)
|
|
|
|
modimpl_prec = [
|
|
modi
|
|
for modi in nt_prec.formsemestre.modimpls_sorted
|
|
if modi.module.code == modimpl.module.code
|
|
]
|
|
if len(modimpl_prec) > 0: # si une correspondance est trouvée
|
|
modprec_id = modimpl_prec[0].id
|
|
moy_ue_capitalisee = get_moy_ue_from_nt(nt_prec, etudid, modprec_id)
|
|
if (
|
|
moy_ue_capitalisee is None
|
|
) or moy_ue_actuelle >= moy_ue_capitalisee: # on prend la meilleure ue
|
|
note = self.nt.get_etud_mod_moy(
|
|
modimpl_id, etudid
|
|
) # lecture de la note
|
|
coeff = modimpl.module.coefficient # le coeff
|
|
# nota: self.somme_coeffs peut être None
|
|
coeff_norm = (
|
|
coeff / self.somme_coeffs if self.somme_coeffs else 0
|
|
) # le coeff normalisé
|
|
else:
|
|
semtag_prec = SemestreTag(nt_prec, nt_prec.sem)
|
|
(note, coeff_norm) = semtag_prec.get_noteEtCoeff_modimpl(
|
|
modprec_id, etudid, profondeur=profondeur - 1
|
|
) # lecture de la note via le semtag associé au modimpl capitalisé
|
|
|
|
# Sinon - pas de notes à prendre en compte
|
|
return (note, coeff_norm)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
def get_ue_capitalisees(self, etudid) -> list[dict]:
|
|
"""Renvoie la liste des capitalisation effectivement capitalisées par un étudiant"""
|
|
if etudid in self.nt.validations.ue_capitalisees.index:
|
|
return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records")
|
|
return []
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Fonctions d'affichage (et d'export csv) des données du semestre en mode debug
|
|
# -----------------------------------------------------------------------------
|
|
def str_detail_resultat_d_un_tag(self, tag, etudid=None, delim=";"):
|
|
"""Renvoie une chaine de caractère décrivant les résultats d'étudiants à un tag :
|
|
rappelle les notes obtenues dans les modules à prendre en compte, les moyennes et les rangs calculés.
|
|
Si etudid=None, tous les étudiants inscrits dans le semestre sont pris en compte. Sinon seuls les étudiants indiqués sont affichés.
|
|
"""
|
|
# Entete
|
|
chaine = delim.join(["%15s" % "nom", "etudid"]) + delim
|
|
taglist = self.get_all_tags()
|
|
if tag in taglist:
|
|
for mod in self.tagdict[tag].values():
|
|
chaine += mod["module_code"] + delim
|
|
chaine += ("%1.1f" % mod["ponderation"]) + delim
|
|
chaine += "coeff" + delim
|
|
chaine += delim.join(
|
|
["moyenne", "rang", "nbinscrit", "somme_coeff", "somme_coeff"]
|
|
) # ligne 1
|
|
chaine += "\n"
|
|
|
|
# Différents cas de boucles sur les étudiants (de 1 à plusieurs)
|
|
if etudid == None:
|
|
lesEtuds = self.get_etudids()
|
|
elif isinstance(etudid, str) and etudid in self.get_etudids():
|
|
lesEtuds = [etudid]
|
|
elif isinstance(etudid, list):
|
|
lesEtuds = [eid for eid in self.get_etudids() if eid in etudid]
|
|
else:
|
|
lesEtuds = []
|
|
|
|
for etudid in lesEtuds:
|
|
descr = (
|
|
"%15s" % self.nt.get_nom_short(etudid)[:15]
|
|
+ delim
|
|
+ str(etudid)
|
|
+ delim
|
|
)
|
|
if tag in taglist:
|
|
for modimpl_id in self.tagdict[tag]:
|
|
(note, coeff) = self.get_noteEtCoeff_modimpl(modimpl_id, etudid)
|
|
descr += (
|
|
(
|
|
"%2.2f" % note
|
|
if note != None and isinstance(note, float)
|
|
else str(note)
|
|
)
|
|
+ delim
|
|
+ (
|
|
"%1.5f" % coeff
|
|
if coeff != None and isinstance(coeff, float)
|
|
else str(coeff)
|
|
)
|
|
+ delim
|
|
+ (
|
|
"%1.5f" % (coeff * self.somme_coeffs)
|
|
if coeff != None and isinstance(coeff, float)
|
|
else "???" # str(coeff * self._sum_coeff_semestre) # voir avec Cléo
|
|
)
|
|
+ delim
|
|
)
|
|
moy = self.get_moy_from_resultats(tag, etudid)
|
|
rang = self.get_rang_from_resultats(tag, etudid)
|
|
coeff = self.get_coeff_from_resultats(tag, etudid)
|
|
tot = (
|
|
coeff * self.somme_coeffs
|
|
if coeff != None
|
|
and self.somme_coeffs != None
|
|
and isinstance(coeff, float)
|
|
else None
|
|
)
|
|
descr += (
|
|
pe_tagtable.TableTag.str_moytag(
|
|
moy, rang, len(self.get_etudids()), delim=delim
|
|
)
|
|
+ delim
|
|
)
|
|
descr += (
|
|
(
|
|
"%1.5f" % coeff
|
|
if coeff != None and isinstance(coeff, float)
|
|
else str(coeff)
|
|
)
|
|
+ delim
|
|
+ (
|
|
"%.2f" % (tot)
|
|
if tot != None
|
|
else str(coeff) + "*" + str(self.somme_coeffs)
|
|
)
|
|
)
|
|
chaine += descr
|
|
chaine += "\n"
|
|
return chaine
|
|
|
|
def str_tagsModulesEtCoeffs(self):
|
|
"""Renvoie une chaine affichant la liste des tags associés au semestre,
|
|
les modules qui les concernent et les coeffs de pondération.
|
|
Plus concrètement permet d'afficher le contenu de self._tagdict"""
|
|
chaine = "Semestre %s d'id %d" % (self.nom, id(self)) + "\n"
|
|
chaine += " -> somme de coeffs: " + str(self.somme_coeffs) + "\n"
|
|
taglist = self.get_all_tags()
|
|
for tag in taglist:
|
|
chaine += " > " + tag + ": "
|
|
for modid, mod in self.tagdict[tag].items():
|
|
chaine += (
|
|
mod["module_code"]
|
|
+ " ("
|
|
+ str(mod["coeff"])
|
|
+ "*"
|
|
+ str(mod["ponderation"])
|
|
+ ") "
|
|
+ str(modid)
|
|
+ ", "
|
|
)
|
|
chaine += "\n"
|
|
return chaine
|
|
|
|
|
|
# ************************************************************************
|
|
# Fonctions diverses
|
|
# ************************************************************************
|
|
|
|
|
|
# *********************************************
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
def get_moduleimpl(modimpl_id) -> dict:
|
|
"""Renvoie l'objet modimpl dont l'id est modimpl_id"""
|
|
modimpl = db.session.get(ModuleImpl, modimpl_id)
|
|
if modimpl:
|
|
return modimpl
|
|
if SemestreTag.DEBUG:
|
|
log(
|
|
"SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas"
|
|
% (modimpl_id)
|
|
)
|
|
return None
|
|
|
|
|
|
# **********************************************
|
|
def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float:
|
|
"""Renvoie la moyenne de l'UE d'un etudid dans laquelle se trouve
|
|
le module de modimpl_id
|
|
"""
|
|
# ré-écrit
|
|
modimpl = get_moduleimpl(modimpl_id) # le module
|
|
ue_status = nt.get_etud_ue_status(etudid, modimpl.module.ue.id)
|
|
if ue_status is None:
|
|
return None
|
|
return ue_status["moy"]
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
def get_synthese_tags_semestre(formsemestre: FormSemestre):
|
|
"""Etant données les implémentations des modules du semestre (modimpls),
|
|
synthétise les tags les concernant (tags saisis dans le programme pédagogique)
|
|
en les associant aux modimpls qui les concernent (modimpl_id, module_id,
|
|
le code du module, coeff et pondération fournie avec le tag (par défaut 1 si non indiquée)).
|
|
|
|
{ tagname1: { modimpl_id1: { 'module_id': ...,
|
|
'coeff': ...,
|
|
'coeff_norm': ...,
|
|
'ponderation': ...,
|
|
'module_code': ...,
|
|
'ue_xxx': ...},
|
|
}
|
|
}
|
|
|
|
Args:
|
|
formsemestre: Le formsemestre à la base de la recherche des tags
|
|
"""
|
|
synthese_tags = {}
|
|
|
|
"""Instance des modules du semestre"""
|
|
modimpls = formsemestre.modimpls_sorted
|
|
|
|
for modimpl in modimpls:
|
|
modimpl_id = modimpl.id
|
|
|
|
"""Liste des tags pour le module concerné"""
|
|
tags = sco_tag_module.module_tag_list(modimpl.module.id)
|
|
|
|
"""Traitement des tags recensés, chacun pouvant étant de la forme
|
|
"mathématiques", "théorie", "pe:0", "maths:2"
|
|
"""
|
|
for tag in tags:
|
|
"""Extraction du nom du tag et du coeff de pondération"""
|
|
(tagname, ponderation) = sco_tag_module.split_tagname_coeff(tag)
|
|
|
|
"""Ajout d'une clé pour le tag"""
|
|
if tagname not in synthese_tags:
|
|
synthese_tags[tagname] = {}
|
|
|
|
"""Ajout du module (modimpl) au tagname considéré"""
|
|
synthese_tags[tagname][modimpl_id] = {
|
|
"modimpl": modimpl, # les données sur le module
|
|
# "coeff": modimpl.module.coefficient, # le coeff du module dans le semestre
|
|
"ponderation": ponderation, # la pondération demandée pour le tag sur le module
|
|
# "module_code": modimpl.module.code, # le code qui doit se retrouver à l'identique dans des ue capitalisee
|
|
# "ue_id": modimpl.module.ue.id, # les données sur l'ue
|
|
# "ue_code": modimpl.module.ue.ue_code,
|
|
# "ue_acronyme": modimpl.module.ue.acronyme,
|
|
}
|
|
|
|
return synthese_tags
|