Recode SemestreTag

This commit is contained in:
Cléo Baras 2024-01-20 16:34:38 +01:00
parent c9336dd01c
commit 3e55391f7e
5 changed files with 533 additions and 988 deletions

View File

@ -98,7 +98,7 @@ class EtudiantsJuryPE:
pe_tools.pe_print("3) Analyse des parcours individuels des étudiants") pe_tools.pe_print("3) Analyse des parcours individuels des étudiants")
no_etud = 0 no_etud = 0
for (no_etud, etudid) in enumerate(self.etudiants_ids): for no_etud, etudid in enumerate(self.etudiants_ids):
self.add_etudid(etudid, cosemestres) self.add_etudid(etudid, cosemestres)
if (no_etud + 1) % 10 == 0: if (no_etud + 1) % 10 == 0:
pe_tools.pe_print((no_etud + 1), " ", end="") pe_tools.pe_print((no_etud + 1), " ", end="")
@ -115,15 +115,27 @@ class EtudiantsJuryPE:
self.formsemestres_jury_ids = self.get_formsemestres_jury() self.formsemestres_jury_ids = self.get_formsemestres_jury()
# Synthèse # Synthèse
pe_tools.pe_print(f" => {len(self.etudiants_jury_ids)} étudiants à diplômer en {annee_diplome}") pe_tools.pe_print(
f" => {len(self.etudiants_jury_ids)} étudiants à diplômer en {annee_diplome}"
)
nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_ids) nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_ids)
pe_tools.pe_print(f" => {nbre_abandons} étudiants éliminer pour abandon") pe_tools.pe_print(f" => {nbre_abandons} étudiants éliminer pour abandon")
pe_tools.pe_print(f" => quelques étudiants futurs diplômés : " + ", ".join([str(etudid) for etudid in list(self.etudiants_jury_ids)[:10]])) pe_tools.pe_print(
f" => quelques étudiants futurs diplômés : "
+ ", ".join([str(etudid) for etudid in list(self.etudiants_jury_ids)[:10]])
)
pe_tools.pe_print(
f" => semestres dont il faut calculer les moyennes : "
+ ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)])
)
def get_etudids(self, annee_diplome: int, ordre="aucun") -> list: def get_etudids(self, annee_diplome: int = None, ordre="aucun") -> list:
"""Liste des etudid des étudiants qui vont être à traiter au jury PE pour """Liste des etudid des étudiants qui vont être à traiter au jury PE pour
l'année de diplômation donnée et n'ayant ni été réorienté, ni abandonné. l'année de diplômation donnée et n'ayant ni été réorienté, ni abandonné.
Si l'année de diplômation n'est pas précisée (None), inclus les étudiants réorientés
ou ayant abandonné.
Si l'``ordre`` est précisé, trie la liste par ordre alphabétique de etat_civil Si l'``ordre`` est précisé, trie la liste par ordre alphabétique de etat_civil
Args: Args:
@ -135,11 +147,17 @@ class EtudiantsJuryPE:
Note: ex JuryPE.get_etudids_du_jury() Note: ex JuryPE.get_etudids_du_jury()
""" """
if annee_diplome:
etudids = [ etudids = [
etudid etudid
for (etudid, donnees) in self.cursus.items() for (etudid, donnees) in self.cursus.items()
if donnees["diplome"] == annee_diplome and not donnees["abandon"] if donnees["diplome"] == annee_diplome and not donnees["abandon"]
] ]
else:
etudids = [
etudid
for (etudid, donnees) in self.cursus.items()
]
if ordre == "alphabetique": # Tri alphabétique if ordre == "alphabetique": # Tri alphabétique
etudidsAvecNom = [ etudidsAvecNom = [
(etudid, etud["etat_civil"]) (etudid, etud["etat_civil"])
@ -150,6 +168,24 @@ class EtudiantsJuryPE:
etudids = [etud[0] for etud in etudidsAvecNomTrie] etudids = [etud[0] for etud in etudidsAvecNomTrie]
return etudids return etudids
def get_etudiants(self, annee_diplome: int = None) -> dict[Identite]:
"""Identités des étudiants (sous forme d'un dictionnaire `{etudid: Identite(etudid)}`
qui vont être à traiter au jury PE pour
l'année de diplômation donnée et n'ayant ni été réorienté, ni abandonné.
Si l'année de diplômation n'est pas précisée (None), inclus les étudiants réorientés
ou ayant abandonné.
Args:
annee_diplome: Année de diplomation visée pour le jury
Returns:
Un dictionnaire `{etudid: Identite(etudid)}`
"""
etudids = self.get_etudids(annee_diplome=annee_diplome)
etudiants = {etudid: self.identites[etudids] for etudid in etudids}
return etudiants
def add_etudid(self, etudid: int, cosemestres): def add_etudid(self, etudid: int, cosemestres):
"""Ajoute un étudiant à ceux qui devront être traités pendant le jury pouvant être : """Ajoute un étudiant à ceux qui devront être traités pendant le jury pouvant être :
@ -173,35 +209,37 @@ class EtudiantsJuryPE:
Note: ex JuryPE.add_etudid_to_jury() Note: ex JuryPE.add_etudid_to_jury()
""" """
"""L'identité de l'étudiant""" """L'identité de l'étudiant"""
identite = Identite.get_etud(etudid) identite = Identite.get_etud(etudid)
self.identites[etudid] = identite self.identites[etudid] = identite
"""Le cursus global de l'étudiant""" """Le cursus global de l'étudiant (restreint aux semestres APC)"""
semestres_etudiant = { semestres_etudiant = {
frmsem.formsemestre_id: frmsem for frmsem in identite.get_formsemestres() frmsem.formsemestre_id: frmsem
for frmsem in identite.get_formsemestres()
if frmsem.formation.is_apc()
} }
self.cursus[etudid] = { self.cursus[etudid] = {
"etudid": etudid, # les infos sur l'étudiant "etudid": etudid, # les infos sur l'étudiant
"etat_civil": identite.etat_civil, # Ajout à la table jury "etat_civil": identite.etat_civil, # Ajout à la table jury
"diplome": annee_diplome(identite), # Le date prévisionnelle de son diplôme "diplome": annee_diplome(identite), # Le date prévisionnelle de son diplôme
"formsemestres": semestres_etudiant # les semestres de l'étudiant "formsemestres": semestres_etudiant, # les semestres de l'étudiant
} }
""" Est-il réorienté / démissionnaire ou a-t-il arrêté volontairement sa formation ?""" """ Est-il réorienté / démissionnaire ou a-t-il arrêté volontairement sa formation ?"""
self.cursus[etudid]["abandon"] = arret_de_formation( self.cursus[etudid]["abandon"] = arret_de_formation(identite, cosemestres)
identite, cosemestres
)
"""Tri des semestres par n° de semestre""" """Tri des semestres par n° de semestre"""
for nom_sem in pe_tools.TOUS_LES_SEMESTRES: for nom_sem in pe_tools.TOUS_LES_SEMESTRES:
numero_sem = int(nom_sem[1]) + 1 i = int(nom_sem[1]) + 1 # le n° du semestre
self.cursus[etudid][nom_sem] = {fid: semestres_etudiant[fid] semestres_i = {
fid: semestres_etudiant[fid]
for fid in semestres_etudiant for fid in semestres_etudiant
if semestres_etudiant[fid].semestre_id == numero_sem} if semestres_etudiant[fid].semestre_id == i
} # les semestres de n°i de l'étudiant
dernier_semestre_i = get_dernier_semestre(semestres_i)
self.cursus[etudid][nom_sem] = dernier_semestre_i
"""Tri des semestres par aggrégat""" """Tri des semestres par aggrégat"""
for parcours in pe_tools.TOUS_LES_AGGREGATS: for parcours in pe_tools.TOUS_LES_AGGREGATS:
@ -210,7 +248,9 @@ class EtudiantsJuryPE:
self.cursus[etudid][parcours] = {} self.cursus[etudid][parcours] = {}
for nom_sem in noms_semestre_de_aggregat: for nom_sem in noms_semestre_de_aggregat:
self.cursus[etudid][parcours] = self.cursus[etudid][parcours] | self.cursus[etudid][nom_sem] self.cursus[etudid][parcours] = (
self.cursus[etudid][parcours] | self.cursus[etudid][nom_sem]
)
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2: if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2:
pe_tools.pe_print( pe_tools.pe_print(
@ -218,20 +258,64 @@ class EtudiantsJuryPE:
end="", end="",
) )
def get_formsemestres_jury(self, semestres_recherches=None):
def get_formsemestres_jury(self):
"""Ayant connaissance des étudiants dont il faut calculer les moyennes pour """Ayant connaissance des étudiants dont il faut calculer les moyennes pour
le jury PE (attribut `self.etudiant_ids), renvoie l'ensemble des formsemestres le jury PE (attribut `self.etudiant_ids) et de leur cursus,
de leur cursus, dont il faudra calculer la moyenne. renvoie un dictionnaire `{fid: FormSemestre(fid)}`
contenant l'ensemble des formsemestres de leur cursus, dont il faudra calculer
la moyenne. Les formsemestres sont limités à ceux indiqués dans ``semestres_recherches``.
Args:
semestres_recherches: Une liste ou une chaine de caractères parmi :
* None : pour obtenir tous les formsemestres du jury
* 'Si' : pour obtenir les semestres de n° i (par ex. 'S1')
* 'iA' : pour obtenir les semestres de l'année i (par ex. '1A' donne ['S1, 'S2'])
* '3S', '4S' : pour obtenir les combinaisons de semestres définies par les aggrégats
Returns: Returns:
Un ensemble de formsemestres Un dictionnaire de la forme {fid: FormSemestre(fid)}
Remarque:
Une liste de la forme `[ 'Si', 'iA' , ... ]` (combinant les formats précédents) est possible.
""" """
formsemestres = {} if semestres_recherches is None:
"""Appel récursif pour obtenir tous les semestres (validants)"""
semestres = self.get_formsemestres_jury(pe_tools.AGGREGAT_DIPLOMANT)
return semestres
elif isinstance(semestres_recherches, list):
"""Appel récursif sur tous les éléments de la liste"""
semestres = {}
for elmt in semestres_recherches:
semestres_elmt = self.get_formsemestres_jury(elmt)
semestres = semestres | semestres_elmt
return semestres
elif (
isinstance(semestres_recherches, str)
and semestres_recherches in pe_tools.TOUS_LES_AGGREGATS
):
"""Cas d'un aggrégat avec appel récursif sur toutes les entrées de l'aggrégat"""
semestres = self.get_formsemestres_jury(
pe_tools.PARCOURS[semestres_recherches]["aggregat"]
)
return semestres
elif (
isinstance(semestres_recherches, str)
and semestres_recherches in pe_tools.TOUS_LES_SEMESTRES
):
"""semestres_recherches est un nom de semestre de type S1,
pour une recherche parmi les étudiants à prendre en compte
dans le jury (diplômé et redoublants non diplômé)
"""
nom_sem = semestres_recherches
semestres = {}
for etudid in self.etudiants_ids: for etudid in self.etudiants_ids:
formsem_etudid = set(self.cursus[etudid].keys()) semestres = semestres | self.cursus[etudid][nom_sem]
formsemestres = formsemestres | formsem_etudid return semestres
return formsemestres else:
raise ValueError(
"Probleme de paramètres d'appel dans get_formsemestreids_du_jury"
)
def get_etudiants_dans_semestres(semestres: dict[FormSemestre]) -> set: def get_etudiants_dans_semestres(semestres: dict[FormSemestre]) -> set:
@ -249,7 +333,7 @@ def get_etudiants_dans_semestres(semestres: dict[FormSemestre]) -> set:
""" """
etudiants_ids = set() etudiants_ids = set()
for (fid, sem) in semestres.items(): # pour chacun des semestres de la liste for fid, sem in semestres.items(): # pour chacun des semestres de la liste
etudiants_du_sem = {ins.etudid for ins in sem.inscriptions} etudiants_du_sem = {ins.etudid for ins in sem.inscriptions}
pe_print(f" --> {sem} : {len(etudiants_du_sem)} etudiants") pe_print(f" --> {sem} : {len(etudiants_du_sem)} etudiants")
@ -361,3 +445,28 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b
return True return True
return False return False
def get_dernier_semestre(semestres: dict[FormSemestre]):
"""Renvoie le dernier semestre en date d'un dictionnaire
de semestres de la forme {fid: FormSemestre(fid)
Args:
semestres: Un dictionnaire de semestres
Return:
Un dictionnaire {fid: FormSemestre(fid)} contenant le semestre le plus récent
"""
if semestres:
fid_dernier_semestre = list(semestres.keys())[0]
dernier_semestre = {fid_dernier_semestre: semestres[fid_dernier_semestre]}
for fid in semestres:
if (
semestres[fid].date_fin
> dernier_semestre[fid_dernier_semestre].date_fin
):
dernier_semestre = {fid: semestres[fid]}
fid_dernier_semestre = fid
return dernier_semestre
else:
return {}

File diff suppressed because it is too large Load Diff

View File

@ -37,110 +37,100 @@ Created on Fri Sep 9 09:15:05 2016
""" """
from app import db, log from app import db, log
from app.comp import res_sem from app.comp import res_sem, inscr_mod, moy_ue, moy_sem
from app.comp.res_common import ResultatsSemestre
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre, Identite, DispenseUE
from app.models.moduleimpls import ModuleImpl from app.models.moduleimpls import ModuleImpl
from app.pe import pe_tagtable from app.pe import pe_tagtable
from app.pe import pe_tools
from app.scodoc import codes_cursus from app.scodoc import codes_cursus, sco_preferences
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.codes_cursus import UE_SPORT
class SemestreTag(pe_tagtable.TableTag): class SemestreTag(pe_tagtable.TableTag):
"""Un SemestreTag représente un tableau de notes (basé sur notesTable) """Un SemestreTag représente les résultats des étudiants à un semestre, en donnant
modélisant les résultats des étudiants sous forme de moyennes par tag. accès aux moyennes par tag.
Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT.
Attributs récupérés via des NotesTables :
- nt: le tableau de notes du semestre considéré
- nt.inscrlist: étudiants inscrits à ce semestre, par ordre alphabétique (avec demissions)
- nt.identdict: { etudid : ident }
- liste des moduleimpl { ... 'module_id', ...}
Attributs supplémentaires :
- inscrlist/identdict: étudiants inscrits hors démissionnaires ou défaillants
- _tagdict : Dictionnaire résumant les tags et les modules du semestre auxquels ils sont liés
Attributs hérités de TableTag :
- nom :
- resultats: {tag: { etudid: (note_moy, somme_coff), ...} , ...}
- rang
- statistiques
Redéfinition :
- get_etudids() : les etudids des étudiants non défaillants ni démissionnaires
""" """
DEBUG = True
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Fonctions d'initialisation # Fonctions d'initialisation
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def __init__(self, notetable, sem): # Initialisation sur la base d'une notetable def __init__(self, nom: str, formsemestre_id: int):
"""Instantiation d'un objet SemestreTag à partir d'un tableau de note """
et des informations sur le semestre pour le dater Args:
nom: Nom à donner au SemestreTag
formsemestre_id: Identifiant du FormSemestre sur lequel il se base
""" """
pe_tagtable.TableTag.__init__( pe_tagtable.TableTag.__init__(
self, self,
nom="S%d %s %s-%s" nom=nom
% (
sem["semestre_id"],
"ENEPS"
if "ENEPS" in sem["titre"]
else "UFA"
if "UFA" in sem["titre"]
else "FI",
sem["annee_debut"],
sem["annee_fin"],
),
) )
"""Le semestre"""
self.formsemestre_id = formsemestre_id
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Les attributs spécifiques """Les résultats du semestre"""
self.nt = notetable self.nt = load_formsemestre_results(self.formsemestre)
# Les attributs hérités : la liste des étudiants """Les étudiants"""
self.inscrlist = [ self.etuds = self.nt.etuds
etud self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
for etud in self.nt.inscrlist
if self.nt.get_etud_etat(etud["etudid"]) == scu.INSCRIT
]
self.identdict = {
etudid: ident
for (etudid, ident) in self.nt.identdict.items()
if etudid in self.get_etudids()
} # Liste des étudiants non démissionnaires et non défaillants
# Les modules pris en compte dans le calcul des moyennes par tag => ceux des UE standards """Les notes, les modules implémentés triés, les étudiants, les coeffs,
self.modimpls = [ récupérés notamment de py:mod:`res_but`
modimpl """
for modimpl in self.nt.formsemestre.modimpls_sorted self.sem_cube = self.nt.sem_cube
if modimpl.module.ue.type == codes_cursus.UE_STANDARD self.modimpls_sorted = self.nt.formsemestre.modimpls_sorted
] # la liste des modules (objet modimpl) self.modimpl_coefs_df = self.nt.modimpl_coefs_df
self.somme_coeffs = sum(
[
modimpl.module.coefficient
for modimpl in self.modimpls
if modimpl.module.coefficient is not None
]
)
# ----------------------------------------------------------------------------- """Les inscriptions au module et les dispenses d'UE"""
def comp_data_semtag(self): self.modimpl_inscr_df = self.nt.modimpl_inscr_df
"""Calcule tous les données numériques associées au semtag""" self.ues = self.nt.ues
# Attributs relatifs aux tag pour les modules pris en compte self.ues_inscr_parcours_df = self.nt.load_ues_inscr_parcours()
self.tagdict = ( self.dispense_ues = self.nt.dispense_ues
self.do_tagdict()
) # Dictionnaire résumant les tags et les données (normalisées) des modules du semestre auxquels ils sont liés
# Calcul des moyennes de chaque étudiant puis ajoute la moyenne au sens "DUT" """Les tags"""
for tag in self.tagdict: self.tags = get_synthese_tags_semestre(self.nt.formsemestre)
self.add_moyennesTag(tag, self.comp_MoyennesTag(tag, force=True))
self.add_moyennesTag("but", self.get_moyennes_DUT()) """Supprime les tags réservés"""
self.taglist = sorted( for tag in pe_tagtable.TAGS_RESERVES:
list(self.tagdict.keys()) + ["but"] if tag in self.tags:
) # actualise la liste des tags del self.tags[tag]
"""Calcul des moyennes & les classements de chaque étudiant à chaque tag"""
self.moyennes_tags = {}
for tag in self.tags:
pe_tools.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_tools.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),
}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def get_etudids(self): def get_etudids(self):
@ -148,89 +138,64 @@ class SemestreTag(pe_tagtable.TableTag):
return [etud["etudid"] for etud in self.inscrlist] return [etud["etudid"] for etud in self.inscrlist]
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def do_tagdict(self): def compute_moyenne_tag(self, tag: str) -> list:
"""Parcourt les modimpl du semestre (instance des modules d'un programme) et synthétise leurs données sous la """Calcule la moyenne des étudiants pour le tag indiqué,
forme d'un dictionnaire reliant les tags saisis dans le programme aux pour ce SemestreTag.
données des modules qui les concernent, à savoir les modimpl_id, les module_id, le code du module, le coeff,
la 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' : ...},
modimpl_id2 : ....
},
tagname2 : ...
}
Renvoie le dictionnaire ainsi construit.
Rq: choix fait de repérer les modules par rapport à leur modimpl_id (valable uniquement pour un semestre), car Sont pris en compte les modules implémentés associés au tag,
correspond à la majorité des calculs de moyennes pour les étudiants avec leur éventuel coefficient de **repondération**, en utilisant les notes
(seuls ceux qui ont capitalisé des ue auront un régime de calcul différent). chargées pour ce SemestreTag.
"""
tagdict = {}
for modimpl in []: # CB: désactive la recherche des tags -> self.modimpls:
modimpl_id = modimpl.id
# liste des tags pour le modimpl concerné:
tags = sco_tag_module.module_tag_list(modimpl.module.id)
for (
tag
) in tags: # tag de la forme "mathématiques", "théorie", "pe:0", "maths:2"
[tagname, ponderation] = sco_tag_module.split_tagname_coeff(
tag
) # extrait un tagname et un éventuel coefficient de pondération (par defaut: 1)
# tagname = tagname
if tagname not in tagdict: # Ajout d'une clé pour le tag
tagdict[tagname] = {}
# Ajout du modimpl au tagname considéré
tagdict[tagname][modimpl_id] = {
"module_id": modimpl.module.id, # 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 tagdict
# -----------------------------------------------------------------------------
def comp_MoyennesTag(self, tag, force=False) -> list:
"""Calcule et renvoie les "moyennes" de tous les étudiants du SemTag
(non défaillants) à un tag donné, en prenant en compte
tous les modimpl_id concerné par le tag, leur coeff et leur pondération.
Force ou non le calcul de la moyenne lorsque des notes sont manquantes. Force ou non le calcul de la moyenne lorsque des notes sont manquantes.
Renvoie les informations sous la forme d'une liste Renvoie les informations sous la forme d'une liste
[ (moy, somme_coeff_normalise, etudid), ...] [ (moy, somme_coeff_normalise, etudid), ...]
""" """
lesMoyennes = []
for etudid in self.get_etudids():
(
notes,
coeffs_norm,
ponderations,
) = self.get_listesNotesEtCoeffsTagEtudiant(
tag, etudid
) # les notes associées au tag
coeffs = comp_coeff_pond(
coeffs_norm, ponderations
) # les coeff pondérés par les tags
(moyenne, somme_coeffs) = pe_tagtable.moyenne_ponderee_terme_a_terme(
notes, coeffs, force=force
)
lesMoyennes += [
(moyenne, somme_coeffs, etudid)
] # Un tuple (pour classement résumant les données)
return lesMoyennes
# ----------------------------------------------------------------------------- """Adaptation du mask de calcul des moyennes au tag visé"""
def get_moyennes_DUT(self): modimpls_mask = [
"""Lit les moyennes DUT du semestre pour tous les étudiants modimpl.module.ue.type != UE_SPORT
et les renvoie au même format que comp_MoyennesTag""" for modimpl in self.formsemestre.modimpls_sorted
return [
(self.nt.etud_moy_gen[etudid], 1.0, etudid) for etudid in self.get_etudids()
] ]
"""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): 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. """Renvoie un couple donnant la note et le coeff normalisé d'un étudiant à un module d'id modimpl_id.
@ -319,27 +284,6 @@ class SemestreTag(pe_tagtable.TableTag):
return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records") return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records")
return [] return []
# -----------------------------------------------------------------------------
def get_listesNotesEtCoeffsTagEtudiant(self, tag, etudid):
"""Renvoie un triplet (notes, coeffs_norm, ponderations) où notes, coeff_norm et ponderation désignent trois listes
donnant -pour un tag donné- les note, coeff et ponderation de chaque modimpl à prendre en compte dans
le calcul de la moyenne du tag.
Les notes et coeff_norm sont extraits grâce à SemestreTag.get_noteEtCoeff_modimpl (donc dans semestre courant ou UE capitalisée).
Les pondérations sont celles déclarées avec le tag (cf. _tagdict)."""
notes = []
coeffs_norm = []
ponderations = []
for moduleimpl_id, modimpl in self.tagdict[
tag
].items(): # pour chaque module du semestre relatif au tag
(note, coeff_norm) = self.get_noteEtCoeff_modimpl(moduleimpl_id, etudid)
if note != None:
notes.append(note)
coeffs_norm.append(coeff_norm)
ponderations.append(modimpl["ponderation"])
return (notes, coeffs_norm, ponderations)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Fonctions d'affichage (et d'export csv) des données du semestre en mode debug # Fonctions d'affichage (et d'export csv) des données du semestre en mode debug
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -435,8 +379,9 @@ class SemestreTag(pe_tagtable.TableTag):
return chaine return chaine
def str_tagsModulesEtCoeffs(self): 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. """Renvoie une chaine affichant la liste des tags associés au semestre,
Plus concrêtement permet d'afficher le contenu de self._tagdict""" 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 = "Semestre %s d'id %d" % (self.nom, id(self)) + "\n"
chaine += " -> somme de coeffs: " + str(self.somme_coeffs) + "\n" chaine += " -> somme de coeffs: " + str(self.somme_coeffs) + "\n"
taglist = self.get_all_tags() taglist = self.get_all_tags()
@ -463,25 +408,6 @@ class SemestreTag(pe_tagtable.TableTag):
# ********************************************* # *********************************************
def comp_coeff_pond(coeffs, ponderations):
"""
Applique une ponderation (indiquée dans la liste ponderations) à une liste de coefficients :
ex: coeff = [2, 3, 1, None], ponderation = [1, 2, 0, 1] => [2*1, 3*2, 1*0, None]
Les coeff peuvent éventuellement être None auquel cas None est conservé ;
Les pondérations sont des floattants
"""
if (
coeffs == None
or ponderations == None
or not isinstance(coeffs, list)
or not isinstance(ponderations, list)
or len(coeffs) != len(ponderations)
):
raise ValueError("Erreur de paramètres dans comp_coeff_pond")
return [
(None if coeffs[i] == None else coeffs[i] * ponderations[i])
for i in range(len(coeffs))
]
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -509,3 +435,58 @@ def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float:
if ue_status is None: if ue_status is None:
return None return None
return ue_status["moy"] 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

View File

@ -41,36 +41,52 @@ import datetime
import numpy as np import numpy as np
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
import pandas as pd
TAGS_RESERVES = ["but"]
class TableTag(object): class TableTag(object):
""" """
Classe mémorisant les moyennes des étudiants à différents tag et permettant de calculer les rangs et les statistiques : Classe mémorisant les moyennes des étudiants à différents tags et permettant de
- nom : Nom représentatif des données de la Table calculer des rangs et des statistiques.
- inscrlist : Les étudiants inscrits dans le TagTag avec leur information de la forme :
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 :
{ etudid : dictionnaire d'info extrait de Scodoc, ...} { etudid : dictionnaire d'info extrait de Scodoc, ...}
- taglist : Liste triée des noms des tags * taglist : Liste triée des noms des tags
- resultats : Dictionnaire donnant les notes-moyennes de chaque étudiant par tag et la somme commulée * 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 : des coeff utilisées dans le calcul de la moyenne pondérée, sous la forme :
{ tag : { etudid: (note_moy, somme_coeff_norm), { tag : { etudid: (note_moy, somme_coeff_norm),
...} ...}
- rangs : Dictionnaire donnant les rang par tag de chaque étudiant de la forme : * rangs : Dictionnaire donnant les rang par tag de chaque étudiant de la forme :
{ tag : {etudid: rang, ...} } { tag : {etudid: rang, ...} }
- nbinscrits : Nombre d'inscrits dans le semestre (pas de distinction entre les tags) * 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 : * statistiques : Dictionnaire donnant les statistiques (moyenne, min, max) des résultats par tag de la forme :
{ tag : (moy, min, max), ...} { tag : (moy, min, max), ...}
""" """
def __init__(self, nom=""): def __init__(self, nom: str):
"""Les attributs basiques des TagTable, qui seront initialisés
dans les classes dérivées
"""
self.nom = nom self.nom = nom
self.inscrlist = [] """Les étudiants"""
self.identdict = {} self.etudiants = {}
self.taglist = [] """Les moyennes par tag"""
self.moyennes_tags = {}
# -----------------------------------------------------------------------------------------------------------
def get_all_tags(self):
"""Renvoie la liste des tags du semestre triée par ordre alphabétique"""
# return self.taglist
return sorted(self.moyennes_tags.keys())
self.resultats = {}
self.rangs = {}
self.statistiques = {}
# ***************************************************************************************************************** # *****************************************************************************************************************
# Accesseurs # Accesseurs
@ -80,8 +96,8 @@ class TableTag(object):
def get_moy_from_resultats(self, tag, etudid): 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""" """Renvoie la moyenne obtenue par un étudiant à un tag donné au regard du format de self.resultats"""
return ( return (
self.resultats[tag][etudid][0] self.moyennes_tags[tag][etudid][0]
if tag in self.resultats and etudid in self.resultats[tag] if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
else None else None
) )
@ -90,7 +106,7 @@ class TableTag(object):
"""Renvoie le rang à un tag d'un étudiant au regard du format de self.resultats""" """Renvoie le rang à un tag d'un étudiant au regard du format de self.resultats"""
return ( return (
self.rangs[tag][etudid] self.rangs[tag][etudid]
if tag in self.resultats and etudid in self.resultats[tag] if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
else None else None
) )
@ -100,16 +116,11 @@ class TableTag(object):
au regard du format de self.resultats. au regard du format de self.resultats.
""" """
return ( return (
self.resultats[tag][etudid][1] self.moyennes_tags[tag][etudid][1]
if tag in self.resultats and etudid in self.resultats[tag] if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
else None 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): def get_nbinscrits(self):
@ -170,10 +181,12 @@ class TableTag(object):
avec calcul du rang avec calcul du rang
:param tag: Un tag :param tag: Un tag
:param listMoyEtCoeff: Une liste donnant [ (moy, coeff, etudid) ] :param listMoyEtCoeff: Une liste donnant [ (moy, coeff, etudid) ]
TODO:: Inutile maintenant ?
""" """
# ajout des moyennes au dictionnaire résultat # ajout des moyennes au dictionnaire résultat
if listMoyEtCoeff: if listMoyEtCoeff:
self.resultats[tag] = { self.moyennes_tags[tag] = {
etudid: (moyenne, somme_coeffs) etudid: (moyenne, somme_coeffs)
for (moyenne, somme_coeffs, etudid) in listMoyEtCoeff for (moyenne, somme_coeffs, etudid) in listMoyEtCoeff
} }
@ -204,11 +217,12 @@ class TableTag(object):
self.statistiques self.statistiques
""" """
stats = ("-NA-", "-", "-") stats = ("-NA-", "-", "-")
if tag not in self.resultats: if tag not in self.moyennes_tags:
return stats return stats
notes = [ notes = [
self.get_moy_from_resultats(tag, etudid) for etudid in self.resultats[tag] self.get_moy_from_resultats(tag, etudid)
for etudid in self.moyennes_tags[tag]
] # les notes du tag ] # les notes du tag
notes_valides = [ notes_valides = [
note for note in notes if isinstance(note, float) and note != None note for note in notes if isinstance(note, float) and note != None
@ -225,7 +239,7 @@ class TableTag(object):
"""Renvoie une chaine de caractères (valable pour un csv) """Renvoie une chaine de caractères (valable pour un csv)
décrivant la moyenne et le rang d'un étudiant, pour un tag donné ; 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]: if tag not in self.get_all_tags() or etudid not in self.moyennes_tags[tag]:
return "" return ""
moystr = TableTag.str_moytag( moystr = TableTag.str_moytag(
@ -256,30 +270,18 @@ class TableTag(object):
str_moytag = classmethod(str_moytag) str_moytag = classmethod(str_moytag)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
def str_tagtable(self, delim=";", decimal_sep=","): def str_tagtable(self):
"""Renvoie une chaine de caractère listant toutes les moyennes, les rangs des étudiants pour tous les tags.""" """Renvoie une chaine de caractère listant toutes les moyennes,
entete = ["etudid", "nom", "prenom"] les rangs des étudiants pour tous les tags."""
etudiants = self.etudiants
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
for tag in self.get_all_tags(): for tag in self.get_all_tags():
entete += [titre + "_" + tag for titre in ["note", "rang", "nb_inscrit"]] df = df.join(self.moyennes_tags[tag]["notes"].rename(f"moy {tag}"))
chaine = delim.join(entete) + "\n" df = df.join(self.moyennes_tags[tag]["classements"].rename(f"class {tag}"))
for etudid in self.identdict: return df.to_csv(sep=";")
descr = delim.join(
[
str(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
# ************************************************************************ # ************************************************************************

View File

@ -292,24 +292,35 @@ def get_etud_tagged_modules(etudid, tagname):
return R return R
def split_tagname_coeff(tag, separateur=":"): def split_tagname_coeff(tag: str, separateur=":") -> tuple[str, float]:
"""Découpe un tag saisi par un utilisateur pour en extraire un tagname """Découpage d'un tag, tel que saisi par un utilisateur dans le programme,
(chaine de caractère correspondant au tag) pour en extraire :
et un éventuel coefficient de pondération, avec le séparateur fourni (par défaut ":").
Renvoie le résultat sous la forme d'une liste [tagname, pond] où pond est un float
Auteur: CB * son _nom de tag_ (tagname) (chaine de caractère correspondant au tag)
* un éventuel coefficient de pondération, avec le séparateur fourni (par défaut ":").
Args:
tag: La saisie utilisateur du tag dans le programme
separateur: Le séparateur des informations dans la saisie utilisateur
Return:
Tuple (tagname, coeff_de_ponderation) extrait de la saisie utilisateur
(avec coeff_de_ponderation=1.0 si non mentionné)
Author:
Cléo Baras
""" """
if separateur in tag: if separateur in tag:
temp = tag.split(":") temp = tag.split(":")
try: try:
pond = float(temp[1]) pond = float(temp[1])
return [temp[0], pond] return (temp[0], pond)
except: except:
return [tag, 1.0] # renvoie tout le tag si le découpage à échouer """Renvoie tout le tag si le découpage à échouer"""
return (tag, 1.0)
else: else:
# initialise le coeff de pondération à 1 lorsqu'aucun coeff de pondération n'est indiqué dans le tag """initialise le coeff de pondération à 1 lorsqu'aucun coeff de pondération n'est indiqué dans le tag"""
return [tag, 1.0] return (tag, 1.0)
"""Tests: """Tests: