Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
5 changed files with 533 additions and 988 deletions
Showing only changes of commit 3e55391f7e - Show all commits

View File

@ -98,7 +98,7 @@ class EtudiantsJuryPE:
pe_tools.pe_print("3) Analyse des parcours individuels des étudiants")
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)
if (no_etud + 1) % 10 == 0:
pe_tools.pe_print((no_etud + 1), " ", end="")
@ -115,15 +115,27 @@ class EtudiantsJuryPE:
self.formsemestres_jury_ids = self.get_formsemestres_jury()
# 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)
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
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
Args:
@ -135,11 +147,17 @@ class EtudiantsJuryPE:
Note: ex JuryPE.get_etudids_du_jury()
"""
if annee_diplome:
etudids = [
etudid
for (etudid, donnees) in self.cursus.items()
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
etudidsAvecNom = [
(etudid, etud["etat_civil"])
@ -150,6 +168,24 @@ class EtudiantsJuryPE:
etudids = [etud[0] for etud in etudidsAvecNomTrie]
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):
"""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()
"""
"""L'identité de l'étudiant"""
identite = Identite.get_etud(etudid)
self.identites[etudid] = identite
"""Le cursus global de l'étudiant"""
"""Le cursus global de l'étudiant (restreint aux semestres APC)"""
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] = {
"etudid": etudid, # les infos sur l'étudiant
"etat_civil": identite.etat_civil, # Ajout à la table jury
"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 ?"""
self.cursus[etudid]["abandon"] = arret_de_formation(
identite, cosemestres
)
self.cursus[etudid]["abandon"] = arret_de_formation(identite, cosemestres)
"""Tri des semestres par n° de semestre"""
for nom_sem in pe_tools.TOUS_LES_SEMESTRES:
numero_sem = int(nom_sem[1]) + 1
self.cursus[etudid][nom_sem] = {fid: semestres_etudiant[fid]
i = int(nom_sem[1]) + 1 # le n° du semestre
semestres_i = {
fid: semestres_etudiant[fid]
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"""
for parcours in pe_tools.TOUS_LES_AGGREGATS:
@ -210,7 +248,9 @@ class EtudiantsJuryPE:
self.cursus[etudid][parcours] = {}
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:
pe_tools.pe_print(
@ -218,20 +258,64 @@ class EtudiantsJuryPE:
end="",
)
def get_formsemestres_jury(self):
def get_formsemestres_jury(self, semestres_recherches=None):
"""Ayant connaissance des étudiants dont il faut calculer les moyennes pour
le jury PE (attribut `self.etudiant_ids), renvoie l'ensemble des formsemestres
de leur cursus, dont il faudra calculer la moyenne.
le jury PE (attribut `self.etudiant_ids) et de leur cursus,
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:
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:
formsem_etudid = set(self.cursus[etudid].keys())
formsemestres = formsemestres | formsem_etudid
return formsemestres
semestres = semestres | self.cursus[etudid][nom_sem]
return semestres
else:
raise ValueError(
"Probleme de paramètres d'appel dans get_formsemestreids_du_jury"
)
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()
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}
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 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.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.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.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_utils as scu
from app.scodoc.codes_cursus import UE_SPORT
class SemestreTag(pe_tagtable.TableTag):
"""Un SemestreTag représente un tableau de notes (basé sur notesTable)
modélisant les résultats des étudiants sous forme de moyennes par tag.
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
"""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.
"""
DEBUG = True
# -----------------------------------------------------------------------------
# Fonctions d'initialisation
# -----------------------------------------------------------------------------
def __init__(self, notetable, sem): # Initialisation sur la base d'une notetable
"""Instantiation d'un objet SemestreTag à partir d'un tableau de note
et des informations sur le semestre pour le dater
def __init__(self, nom: str, formsemestre_id: int):
"""
Args:
nom: Nom à donner au SemestreTag
formsemestre_id: Identifiant du FormSemestre sur lequel il se base
"""
pe_tagtable.TableTag.__init__(
self,
nom="S%d %s %s-%s"
% (
sem["semestre_id"],
"ENEPS"
if "ENEPS" in sem["titre"]
else "UFA"
if "UFA" in sem["titre"]
else "FI",
sem["annee_debut"],
sem["annee_fin"],
),
nom=nom
)
"""Le semestre"""
self.formsemestre_id = formsemestre_id
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Les attributs spécifiques
self.nt = notetable
"""Les résultats du semestre"""
self.nt = load_formsemestre_results(self.formsemestre)
# Les attributs hérités : la liste des étudiants
self.inscrlist = [
etud
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 étudiants"""
self.etuds = self.nt.etuds
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
# Les modules pris en compte dans le calcul des moyennes par tag => ceux des UE standards
self.modimpls = [
modimpl
for modimpl in self.nt.formsemestre.modimpls_sorted
if modimpl.module.ue.type == codes_cursus.UE_STANDARD
] # la liste des modules (objet modimpl)
self.somme_coeffs = sum(
[
modimpl.module.coefficient
for modimpl in self.modimpls
if modimpl.module.coefficient is not None
]
)
"""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
# -----------------------------------------------------------------------------
def comp_data_semtag(self):
"""Calcule tous les données numériques associées au semtag"""
# Attributs relatifs aux tag pour les modules pris en compte
self.tagdict = (
self.do_tagdict()
) # Dictionnaire résumant les tags et les données (normalisées) des modules du semestre auxquels ils sont liés
"""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
# Calcul des moyennes de chaque étudiant puis ajoute la moyenne au sens "DUT"
for tag in self.tagdict:
self.add_moyennesTag(tag, self.comp_MoyennesTag(tag, force=True))
self.add_moyennesTag("but", self.get_moyennes_DUT())
self.taglist = sorted(
list(self.tagdict.keys()) + ["but"]
) # actualise la liste des tags
"""Les tags"""
self.tags = get_synthese_tags_semestre(self.nt.formsemestre)
"""Supprime les tags réservés"""
for tag in pe_tagtable.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_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):
@ -148,89 +138,64 @@ class SemestreTag(pe_tagtable.TableTag):
return [etud["etudid"] for etud in self.inscrlist]
# -----------------------------------------------------------------------------
def do_tagdict(self):
"""Parcourt les modimpl du semestre (instance des modules d'un programme) et synthétise leurs données sous la
forme d'un dictionnaire reliant les tags saisis dans le programme aux
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.
def compute_moyenne_tag(self, tag: str) -> list:
"""Calcule la moyenne des étudiants pour le tag indiqué,
pour ce SemestreTag.
Rq: choix fait de repérer les modules par rapport à leur modimpl_id (valable uniquement pour un semestre), car
correspond à la majorité des calculs de moyennes pour les étudiants
(seuls ceux qui ont capitalisé des ue auront un régime de calcul différent).
"""
tagdict = {}
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.
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.
Renvoie les informations sous la forme d'une liste
[ (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
# -----------------------------------------------------------------------------
def get_moyennes_DUT(self):
"""Lit les moyennes DUT du semestre pour tous les étudiants
et les renvoie au même format que comp_MoyennesTag"""
return [
(self.nt.etud_moy_gen[etudid], 1.0, etudid) for etudid in self.get_etudids()
"""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.
@ -319,27 +284,6 @@ class SemestreTag(pe_tagtable.TableTag):
return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records")
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
# -----------------------------------------------------------------------------
@ -435,8 +379,9 @@ class SemestreTag(pe_tagtable.TableTag):
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"""
"""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()
@ -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:
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

View File

@ -41,36 +41,52 @@ import datetime
import numpy as np
from app.scodoc import sco_utils as scu
import pandas as pd
TAGS_RESERVES = ["but"]
class TableTag(object):
"""
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 :
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 :
{ 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
* 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 :
* 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 :
* 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 :
{ 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.inscrlist = []
self.identdict = {}
self.taglist = []
"""Les étudiants"""
self.etudiants = {}
"""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
@ -80,8 +96,8 @@ class TableTag(object):
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]
self.moyennes_tags[tag][etudid][0]
if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
else None
)
@ -90,7 +106,7 @@ class TableTag(object):
"""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]
if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
else None
)
@ -100,16 +116,11 @@ class TableTag(object):
au regard du format de self.resultats.
"""
return (
self.resultats[tag][etudid][1]
if tag in self.resultats and etudid in self.resultats[tag]
self.moyennes_tags[tag][etudid][1]
if tag in self.moyennes_tags and etudid in self.moyennes_tags[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):
@ -170,10 +181,12 @@ class TableTag(object):
avec calcul du rang
:param tag: Un tag
:param listMoyEtCoeff: Une liste donnant [ (moy, coeff, etudid) ]
TODO:: Inutile maintenant ?
"""
# ajout des moyennes au dictionnaire résultat
if listMoyEtCoeff:
self.resultats[tag] = {
self.moyennes_tags[tag] = {
etudid: (moyenne, somme_coeffs)
for (moyenne, somme_coeffs, etudid) in listMoyEtCoeff
}
@ -204,11 +217,12 @@ class TableTag(object):
self.statistiques
"""
stats = ("-NA-", "-", "-")
if tag not in self.resultats:
if tag not in self.moyennes_tags:
return stats
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
notes_valides = [
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)
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 ""
moystr = TableTag.str_moytag(
@ -256,30 +270,18 @@ class TableTag(object):
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"]
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"])
for tag in self.get_all_tags():
entete += [titre + "_" + tag for titre in ["note", "rang", "nb_inscrit"]]
chaine = delim.join(entete) + "\n"
df = df.join(self.moyennes_tags[tag]["notes"].rename(f"moy {tag}"))
df = df.join(self.moyennes_tags[tag]["classements"].rename(f"class {tag}"))
for etudid in self.identdict:
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
return df.to_csv(sep=";")
# ************************************************************************

View File

@ -292,24 +292,35 @@ def get_etud_tagged_modules(etudid, tagname):
return R
def split_tagname_coeff(tag, separateur=":"):
"""Découpe un tag saisi par un utilisateur pour en extraire un tagname
(chaine de caractère correspondant au tag)
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
def split_tagname_coeff(tag: str, separateur=":") -> tuple[str, float]:
"""Découpage d'un tag, tel que saisi par un utilisateur dans le programme,
pour en extraire :
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:
temp = tag.split(":")
try:
pond = float(temp[1])
return [temp[0], pond]
return (temp[0], pond)
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:
# initialise le coeff de pondération à 1 lorsqu'aucun coeff de pondération n'est indiqué dans le tag
return [tag, 1.0]
"""initialise le coeff de pondération à 1 lorsqu'aucun coeff de pondération n'est indiqué dans le tag"""
return (tag, 1.0)
"""Tests: