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
9 changed files with 141 additions and 514 deletions
Showing only changes of commit cb5df2fffd - Show all commits

View File

@ -1,5 +1,16 @@
from app.models import Formation, FormSemestre
from app.scodoc import codes_cursus
from app import log
PE_DEBUG = 0
if not PE_DEBUG:
# log to notes.log
def pe_print(*a, **kw):
# kw is ignored. log always add a newline
log(" ".join(a))
else:
pe_print = print # print function
def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str:
@ -29,7 +40,7 @@ def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str:
f"{semestre.date_debut.year}-{semestre.date_fin.year}",
]
if avec_fid:
description.append(f"({semestre.forsemestre_id})")
description.append(f"({semestre.formsemestre_id})")
return " ".join(description)

View File

@ -45,21 +45,11 @@ import unicodedata
from flask import g
import app.scodoc.sco_utils as scu
from app import log
from app.models import FormSemestre
from app.scodoc import sco_formsemestre
from app.scodoc.sco_logos import find_logo
PE_DEBUG = 0
if not PE_DEBUG:
# log to notes.log
def pe_print(*a, **kw):
# kw is ignored. log always add a newline
log(" ".join(a))
else:
pe_print = print # print function
# Generated LaTeX files are encoded as:

View File

@ -35,10 +35,9 @@ Created on 17/01/2024
@author: barasc
"""
import app.pe.pe_comp as pe_comp
from app.models import FormSemestre, Identite
from app.pe.pe_comp import pe_print
import app.pe.pe_affichage as pe_affichage
import app.pe.pe_comp as pe_comp
class EtudiantsJuryPE:
@ -86,17 +85,17 @@ class EtudiantsJuryPE:
cosemestres = pe_comp.get_cosemestres_diplomants(self.annee_diplome, None)
self.cosemestres = cosemestres
pe_comp.pe_print(f"1) Recherche des coSemestres -> {len(cosemestres)} trouvés")
pe_affichage.pe_print(f"1) Recherche des coSemestres -> {len(cosemestres)} trouvés")
pe_comp.pe_print("2) Liste des étudiants dans les différents co-semestres")
pe_affichage.pe_print("2) Liste des étudiants dans les différents co-semestres")
self.etudiants_ids = get_etudiants_dans_semestres(cosemestres)
pe_comp.pe_print(
pe_affichage.pe_print(
" => %d étudiants trouvés dans les cosemestres" % len(self.etudiants_ids)
)
# Analyse des parcours étudiants pour déterminer leur année effective de diplome
# avec prise en compte des redoublements, des abandons, ....
pe_comp.pe_print("3) Analyse des parcours individuels des étudiants")
pe_affichage.pe_print("3) Analyse des parcours individuels des étudiants")
no_etud = 0
for no_etud, etudid in enumerate(self.etudiants_ids):
@ -110,10 +109,6 @@ class EtudiantsJuryPE:
# Analyse son parcours pour atteindre chaque semestre de la formation
self.structure_cursus_etudiant(etudid)
if (no_etud + 1) % 10 == 0:
pe_comp.pe_print(f"{no_etud + 1}")
no_etud += 1
pe_comp.pe_print()
# Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris
self.etudiants_diplomes = self.get_etudiants_diplomes()
@ -126,23 +121,23 @@ class EtudiantsJuryPE:
"""Les formsemestres (des étudiants) dont il faut calculer les moyennes"""
# Synthèse
pe_comp.pe_print(
pe_affichage.pe_print(
f" => {len(self.etudiants_diplomes)} étudiants à diplômer en {self.annee_diplome}"
)
nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_diplomes)
pe_comp.pe_print(f" => {nbre_abandons} étudiants éliminer pour abandon")
pe_comp.pe_print(
pe_affichage.pe_print(f" => {nbre_abandons} étudiants non considérés (redoublement, réorientation, abandon")
pe_affichage.pe_print(
f" => {len(self.formsemestres_jury_ids)} semestres dont il faut calculer la moyenne"
)
pe_comp.pe_print(
" => quelques étudiants futurs diplômés : "
+ ", ".join([str(etudid) for etudid in list(self.etudiants_diplomes)[:10]])
)
pe_comp.pe_print(
" => semestres dont il faut calculer les moyennes : "
+ ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)])
)
# Les abandons :
# pe_affichage.pe_print(
# " => quelques étudiants futurs diplômés : "
# + ", ".join([str(etudid) for etudid in list(self.etudiants_diplomes)[:10]])
# )
# pe_affichage.pe_print(
# " => semestres dont il faut calculer les moyennes : "
# + ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)])
# )
# Les abandons (pour debug)
self.abandons = sorted(
[
cursus["nom"]
@ -416,7 +411,7 @@ def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set:
for sem in semestres.values(): # 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")
pe_affichage.pe_print(f" --> {sem} : {len(etudiants_du_sem)} etudiants")
etudiants_ids = (
etudiants_ids | etudiants_du_sem
) # incluant la suppression des doublons

View File

@ -1,8 +1,9 @@
from app.comp import moy_sem
from app.pe.pe_tabletags import TableTag
from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE
from app.pe.pe_trajectoiretag import TrajectoireTag
from app.comp import moy_sem
import pandas as pd
import numpy as np

View File

@ -46,29 +46,19 @@ import io
import os
from zipfile import ZipFile
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models.etudiants import Identite
from app.scodoc.gen_tables import GenTable, SeqGenTable
import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import SeqGenTable
from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import TrajectoiresJuryPE, Trajectoire
import app.pe.pe_comp as pe_comp
import app.pe.pe_affichage as pe_affichage
from app.pe.pe_semtag import SemestreTag
from app.pe.pe_interclasstag import AggregatInterclasseTag
from app.pe.pe_trajectoiretag import TrajectoireTag
import app.pe.pe_affichage as pe_affichage
import pandas as pd
import numpy as np
# ----------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------
class JuryPE(object):
"""Classe mémorisant toutes les informations nécessaires pour établir un jury de PE.
Modèle basé sur NotesTable.
@ -112,7 +102,7 @@ class JuryPE(object):
self.zipdata = io.BytesIO()
with ZipFile(self.zipdata, "w") as zipfile:
# Chargement des étudiants à prendre en compte dans le jury
pe_comp.pe_print(
pe_affichage.pe_print(
f"""*** Recherche et chargement des étudiants diplômés en {
self.diplome} pour la formation {self.formation_id}"""
)
@ -123,42 +113,47 @@ class JuryPE(object):
self.diplomes_ids = self.etudiants.diplomes_ids
# Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE
pe_comp.pe_print("*** Génère les semestres taggués")
pe_affichage.pe_print("*** Génère les semestres taggués")
self.semestres_taggues = compute_semestres_tag(self.etudiants)
if pe_comp.PE_DEBUG:
# Intègre le bilan des semestres taggués au zip final
output = io.BytesIO()
with pd.ExcelWriter(output, engine="openpyxl") as writer:
for formsemestretag in self.semestres_taggues.values():
filename = formsemestretag.nom.replace(" ", "_") + ".csv"
pe_comp.pe_print(f" - Export csv de {filename} ")
onglet = formsemestretag.nom
df = formsemestretag.df_semtag()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
output.seek(0)
self.add_file_to_zip(
zipfile,
filename,
formsemestretag.str_tagtable(),
path="details_semestres",
f"semestres_taggues_{self.diplome}.xlsx",
output.read(),
path="details",
)
# Génère les trajectoires (combinaison de semestres suivis
# par un étudiant pour atteindre le semestre final d'un aggrégat)
pe_comp.pe_print(
pe_affichage.pe_print(
"*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants"
)
self.trajectoires = TrajectoiresJuryPE(self.diplome)
self.trajectoires.cree_trajectoires(self.etudiants)
# Génère les moyennes par tags des trajectoires
pe_comp.pe_print(
pe_affichage.pe_print(
"*** Calcule les moyennes par tag des trajectoires possibles"
)
self.trajectoires_tagguees = compute_trajectoires_tag(
self.trajectoires, self.etudiants, self.semestres_taggues
)
if pe_comp.PE_DEBUG:
if pe_affichage.PE_DEBUG:
# Intègre le bilan des trajectoires tagguées au zip final
for trajectoire_tagguee in self.trajectoires_tagguees.values():
filename = trajectoire_tagguee.get_repr().replace(" ", "_") + ".csv"
pe_comp.pe_print(f" - Export csv de {filename} ")
# pe_affichage.pe_print(f" - Export csv de {filename} ")
self.add_file_to_zip(
zipfile,
filename,
@ -167,16 +162,16 @@ class JuryPE(object):
)
# Génère les interclassements (par promo et) par (nom d') aggrégat
pe_comp.pe_print("*** Génère les interclassements par aggrégat")
pe_affichage.pe_print("*** Génère les interclassements par aggrégat")
self.interclassements_taggues = compute_interclassements(
self.etudiants, self.trajectoires, self.trajectoires_tagguees
)
if pe_comp.PE_DEBUG:
if pe_affichage.PE_DEBUG:
# Intègre le bilan des aggrégats (par promo) au zip final
for interclass_tag in self.interclassements_taggues.values():
filename = interclass_tag.get_repr().replace(" ", "_") + ".csv"
pe_comp.pe_print(f" - Export csv de {filename} ")
# pe_affichage.pe_print(f" - Export csv de {filename} ")
self.add_file_to_zip(
zipfile,
filename,
@ -188,7 +183,7 @@ class JuryPE(object):
self.synthese = self.synthetise_juryPE()
# Export des données => mode 1 seule feuille -> supprimé
pe_comp.pe_print("*** Export du jury de synthese")
pe_affichage.pe_print("*** Export du jury de synthese")
output = io.BytesIO()
with pd.ExcelWriter(output, engine="openpyxl") as writer:
@ -241,15 +236,15 @@ class JuryPE(object):
def synthetise_juryPE(self):
"""Synthétise tous les résultats du jury PE dans des dataframes"""
pe_comp.pe_print("*** Synthèse finale des moyennes ***")
pe_affichage.pe_print("*** Synthèse finale des moyennes ***")
synthese = {}
pe_comp.pe_print(" -> Synthèse des données administratives")
pe_affichage.pe_print(" -> Synthèse des données administratives")
synthese["administratif"] = self.df_administratif()
tags = self.do_tags_list(self.interclassements_taggues)
for tag in tags:
pe_comp.pe_print(f" -> Synthèse du tag {tag}")
pe_affichage.pe_print(f" -> Synthèse du tag {tag}")
synthese[tag] = self.df_tag(tag)
return synthese
@ -325,7 +320,7 @@ class JuryPE(object):
donnees[etudid] |= {
f"{aggregat} notes ": "-",
f"{aggregat} class. (groupe)": "-",
f"{aggregat} min/moy/max (groupe)": "-",
f"{aggregat} min | moy | max (groupe)": "-",
}
if trajectoire:
trajectoire_tagguee = self.trajectoires_tagguees[
@ -335,24 +330,23 @@ class JuryPE(object):
bilan = trajectoire_tagguee.moyennes_tags[tag]
donnees[etudid] |= {
f"{aggregat} notes ": round(bilan['notes'].loc[etudid], 2),
f"{aggregat} notes ": round(bilan["notes"].loc[etudid], 2),
f"{aggregat} class. (groupe)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
f"{aggregat} min/moy/max (groupe)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}",
f"{aggregat} min | moy | max (groupe)": f"{bilan['min']:.1f} | {bilan['moy']:.1f} | {bilan['max']:.1f}",
}
"""L'interclassement"""
interclass = self.interclassements_taggues[aggregat]
donnees[etudid] |= {
f"{aggregat} class. (promo)": "-",
f"{aggregat} min/moy/max (promo)": "-",
f"{aggregat} min | moy | max (promo)": "-",
}
if tag in interclass.moyennes_tags:
bilan = interclass.moyennes_tags[tag]
donnees[etudid] |= {
f"{aggregat} class. (promo)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
f"{aggregat} min/moy/max (promo)": f"{bilan['min']:.1f}/{bilan['moy']:.1f}/{bilan['max']:.1f}",
f"{aggregat} min | moy | max (promo)": f"{bilan['min']:.1f} | {bilan['moy']:.1f} | {bilan['max']:.1f}",
}
# Fin de l'aggrégat
@ -390,7 +384,7 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
"""
"""Création des semestres taggués, de type 'S1', 'S2', ..."""
pe_comp.pe_print("*** Création des semestres taggués")
pe_affichage.pe_print("*** Création des semestres taggués")
formsemestres = etudiants.get_formsemestres(
semestres_recherches=pe_comp.TOUS_LES_SEMESTRES
@ -398,19 +392,11 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
semestres_tags = {}
for frmsem_id, formsemestre in formsemestres.items():
"""Choix d'un nom pour le semestretag"""
nom = "S%d %d %d-%d" % (
formsemestre.semestre_id,
frmsem_id,
formsemestre.date_debut.year,
formsemestre.date_fin.year,
)
pe_comp.pe_print(f" --> Semestre taggué {nom} sur la base de {formsemestre}")
# Crée le semestre_tag et exécute les calculs de moyennes
formsemestretag = SemestreTag(nom, frmsem_id)
formsemestretag = SemestreTag(frmsem_id)
pe_affichage.pe_print(
f" --> Semestre taggué {formsemestretag.nom} sur la base de {formsemestre}"
)
# Stocke le semestre taggué
semestres_tags[frmsem_id] = formsemestretag
@ -443,23 +429,16 @@ def compute_trajectoires_tag(
semestres_tag: Les semestres tag (pour lesquels des moyennes par tag ont été calculés)
Return:
Un dictionnaire de la forme {nom_aggregat: {fid_terminal: SetTag(fid_terminal)} }
Un dictionnaire de la forme ``{nom_aggregat: {fid_terminal: SetTag(fid_terminal)} }``
"""
pe_comp.pe_print(" *** Création des aggrégats ")
trajectoires_tagguees = {}
for trajectoire_id in trajectoires.trajectoires:
trajectoire = trajectoires.trajectoires[trajectoire_id]
for trajectoire_id, trajectoire in trajectoires.trajectoires.items():
nom = trajectoire.get_repr()
pe_comp.pe_print(f" --> Fusion {nom}")
pe_affichage.pe_print(f" --> Aggrégat {nom}")
# Trajectoire_tagguee associée
trajectoire_tagguee = TrajectoireTag(nom, trajectoire, semestres_taggues)
"""Trajectoire_tagguee associée"""
"""Mémorise le résultat"""
# Mémorise le résultat
trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee
return trajectoires_tagguees
@ -474,11 +453,9 @@ def compute_interclassements(
pour fournir un classement sur la promo. Le classement est établi au regard du nombre
d'étudiants ayant participé au même aggrégat.
"""
pe_comp.pe_print(" Interclassement sur la promo")
aggregats_interclasses_taggues = {}
for nom_aggregat in pe_comp.TOUS_LES_SEMESTRES + pe_comp.TOUS_LES_AGGREGATS:
pe_comp.pe_print(f" --> {nom_aggregat}")
pe_affichage.pe_print(f" --> Interclassement {nom_aggregat}")
interclass = AggregatInterclasseTag(
nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees
)

View File

@ -45,61 +45,61 @@ 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)
import app.pe.pe_affichage as pe_affichage
from app.pe.pe_tabletags import TableTag, TAGS_RESERVES
import pandas as pd
class SemestreTag(TableTag):
"""Un SemestreTag représente les résultats des étudiants à un semestre, en donnant
def __init__(self, formsemestre_id: int):
"""
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
formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base
"""
TableTag.__init__(self, nom=nom)
# TableTag.__init__(self, nom=nom)
"""Le semestre"""
# Le semestre
self.formsemestre_id = formsemestre_id
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
"""Les résultats du semestre"""
# Le nom du semestre taggué
self.nom = self.get_repr()
# Les résultats du semestre
self.nt = load_formsemestre_results(self.formsemestre)
"""Les étudiants"""
# 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`
"""
# 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"""
# 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)"""
# 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"""
# 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}")
# pe_affichage.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] = {
@ -111,8 +111,7 @@ class SemestreTag(TableTag):
"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")
# Ajoute les moyennes générales de BUT pour le semestre considéré
moy_gen_but = self.nt.etud_moy_gen
class_gen_but = self.nt.etud_moy_gen_ranks_int
self.moyennes_tags["but"] = {
@ -124,16 +123,18 @@ class SemestreTag(TableTag):
"nb_inscrits": len(moy_gen_but),
}
"""Synthétise l'ensemble des moyennes dans un dataframe"""
# 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)
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]
pe_affichage.pe_print(f" => Traitement des tags {', '.join(self.tags_sorted)}")
def get_repr(self):
"""Nom affiché pour le semestre taggué"""
return pe_affichage.nom_semestre_etape(self.formsemestre, avec_fid=True)
# -----------------------------------------------------------------------------
def compute_moyenne_tag(self, tag: str) -> list:
"""Calcule la moyenne des étudiants pour le tag indiqué,
pour ce SemestreTag.
@ -192,14 +193,22 @@ class SemestreTag(TableTag):
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
TODO:: A rependre si nécessaire
"""
def get_ue_capitalisees(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 []
(note, coeff_norm) = (None, None)
modimpl = get_moduleimpl(modimpl_id) # Le module considéré
@ -207,7 +216,7 @@ class SemestreTag(TableTag):
return (None, None)
# Y-a-t-il eu capitalisation d'UE ?
ue_capitalisees = self.get_ue_capitalisees(
ue_capitalisees = get_ue_capitalisees(
etudid
) # les ue capitalisées des étudiants
ue_capitalisees_id = {
@ -273,130 +282,18 @@ class SemestreTag(TableTag):
# 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 []
def df_semtag(self):
"""Renvoie un dataframe listant toutes les moyennes,
les rangs des étudiants pour tous les tags dans le semestre taggué"""
# -----------------------------------------------------------------------------
# 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"
etudiants = self.etudiants
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
# 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
for tag in self.get_all_tags():
df = df.join(self.moyennes_tags[tag]["notes"].rename(f"Moy {tag}"))
df = df.join(self.moyennes_tags[tag]["classements"].rename(f"Class {tag}"))
return df
# ************************************************************************
# Fonctions diverses
@ -437,8 +334,8 @@ def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float:
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)).
en les associant aux modimpls qui les concernent (modimpl_id) et
aucoeff et pondération fournie avec le tag (par défaut 1 si non indiquée)).
{ tagname1: { modimpl_id1: { 'module_id': ...,
'coeff': ...,
@ -451,6 +348,9 @@ def get_synthese_tags_semestre(formsemestre: FormSemestre):
Args:
formsemestre: Le formsemestre à la base de la recherche des tags
Return:
Un dictionnaire de tags
"""
synthese_tags = {}

View File

@ -91,188 +91,6 @@ class TableTag(object):
return sorted(self.moyennes_tags.keys())
# *****************************************************************************************************************
# 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.moyennes_tags[tag][etudid][0]
if tag in self.moyennes_tags and etudid in self.moyennes_tags[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.moyennes_tags and etudid in self.moyennes_tags[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.moyennes_tags[tag][etudid][1]
if tag in self.moyennes_tags and etudid in self.moyennes_tags[tag]
else None
)
# -----------------------------------------------------------------------------------------------------------
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) -> bool:
"""
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) ]
TODO:: Inutile maintenant ?
"""
# ajout des moyennes au dictionnaire résultat
if listMoyEtCoeff:
self.moyennes_tags[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]
if isinstance(col[0], float)
else 0, # remplace les None et autres chaines par des zéros
) # triées
self.rangs[tag] = scu.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.moyennes_tags:
return stats
notes = [
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
]
nb_notes_valides = len(notes_valides)
if nb_notes_valides > 0:
(moy, _) = 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.moyennes_tags[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 df_tagtable(self):
"""Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags
@ -300,68 +118,3 @@ class TableTag(object):
return df.to_csv(sep=";")
# ************************************************************************
# Fonctions diverses
# ************************************************************************
# *********************************************
def moyenne_ponderee_terme_a_terme(notes, coefs=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 (
coefs != None and not isinstance(coefs, list) and len(coefs) != 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
coefs = [1] * len(notes) if coefs is None else coefs
# S'il n'y a pas de notes
if not notes: # Si notes = []
return (None, None)
# Liste indiquant les notes valides
notes_valides = [
(isinstance(note, float) and not np.isnan(note)) or isinstance(note, int)
for note in notes
]
# Si on force le calcul de la moyenne ou qu'on ne le force pas
# et qu'on a le bon nombre de notes
if force or sum(notes_valides) == len(notes):
moyenne, ponderation = 0.0, 0.0
for i in range(len(notes)):
if notes_valides[i]:
moyenne += coefs[i] * notes[i]
ponderation += coefs[i]
return (
(moyenne / (ponderation * 1.0), ponderation)
if ponderation != 0
else (None, 0)
)
# Si on ne force pas le calcul de la moyenne
return (None, None)
# -------------------------------------------------------------------------------------------
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

View File

@ -1,4 +1,6 @@
import app.pe.pe_comp as pe_comp
import app.pe.pe_affichage as pe_affichage
from app.models import FormSemestre
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
@ -129,7 +131,7 @@ def get_trajectoires_etudid(trajectoires, etudid):
trajectoires suivies par un étudiant
"""
if etudid not in trajectoires.suivi:
pe_comp.pe_print(f"{etudid} fait-il bien partie du jury ?")
pe_affichage.pe_print(f"{etudid} fait-il bien partie du jury ?")
liste = []
for aggregat in pe_comp.TOUS_LES_PARCOURS:

View File

@ -39,12 +39,10 @@ Created on Fri Sep 9 09:15:05 2016
from app.comp import moy_sem
from app.comp.res_sem import load_formsemestre_results
from app.pe.pe_semtag import SemestreTag
from app.pe import pe_tabletags
import pandas as pd
import numpy as np
from app.pe.pe_trajectoire import Trajectoire
from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_tabletags import TableTag