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
6 changed files with 420 additions and 232 deletions
Showing only changes of commit 283daae4d9 - Show all commits

View File

@ -39,15 +39,6 @@ Created on 17/01/2024
import app.pe.pe_tools as pe_tools
from app.models import FormSemestre, Identite
from app.pe.pe_tools import pe_print
from app.scodoc import (
sco_etud,
codes_cursus,
sco_formsemestre,
sco_formsemestre_inscriptions,
sco_report,
)
import datetime
class EtudiantsJuryPE:
"""Classe centralisant la gestion des étudiants à prendre en compte dans un jury de PE"""
@ -63,19 +54,16 @@ class EtudiantsJuryPE:
"Les identités des étudiants traités pour le jury"
self.identites = {} # ex. ETUDINFO_DICT
"Les cursus (semestres suivis, abandons, dernier S1, S2, ...) des étudiants"
"Les cursus (semestres suivis, abandons) des étudiants"
self.cursus = {}
"""Les aggrégats des semestres suivis (par ex: 3S=S1+S2+S3 à prendre en compte avec d'éventuels redoublements) des étudiants"""
self.aggregats = {}
self.trajectoires = {}
"Les etudids des étudiants à considérer au jury (ceux qui seront effectivement diplômés)"
self.diplomes_ids = {}
"Les etudids des étudiants dont il faut calculer les moyennes/classements (même si d'éventuels abandons)"
self.etudiants_ids = {}
"Les formsemestres dont il faut calculer les moyennes par tag"
self.formsemestres_jury_ids = {}
def find_etudiants(self, formation_id: int):
"""Liste des étudiants à prendre en compte dans le jury PE, en les recherchant
de manière automatique par rapport à leur année de diplomation ``annee_diplome``
@ -116,7 +104,7 @@ class EtudiantsJuryPE:
self.analyse_etat_etudiant(etudid, cosemestres)
"""L'analyse de son parcours pour atteindre chaque semestre de la formation"""
self.analyse_parcours_etudiant_dans_semestres(etudid)
self.structure_cursus_etudiant(etudid)
if (no_etud + 1) % 10 == 0:
pe_tools.pe_print((no_etud + 1), " ", end="")
@ -130,7 +118,7 @@ class EtudiantsJuryPE:
self.etudiants_ids = {etudid for etudid in self.identites}
"""Les formsemestres (des étudiants) dont il faut calculer les moyennes"""
self.formsemestres_jury_ids = self.get_formsemestres_jury()
self.formsemestres_jury_ids = self.get_formsemestres()
# Synthèse
pe_tools.pe_print(
@ -210,61 +198,37 @@ class EtudiantsJuryPE:
""" Est-il réorienté / démissionnaire ou a-t-il arrêté volontairement sa formation ?"""
self.cursus[etudid]["abandon"] = arret_de_formation(identite, cosemestres)
def analyse_parcours_etudiant_dans_semestres(self, etudid: int):
"""Structure les informations sur les semestres suivis par un
étudiant, pour identifier les semestres qui seront pris en compte lors de ses calculs
de moyennes PE.
def get_semestres_significatifs(self, etudid: int):
"""Ensemble des semestres d'un étudiant, qui l'auraient amené à être diplomé
l'année visée (supprime les semestres qui conduisent à une diplomation
postérieure à celle du jury visé)
Cette structuration (cf. attribut self.cursus) s'appuie sur les numéros de semestre: pour chaque Si, stocke :
le dernier semestre (en date) de numéro i qu'il a suivi (1 ou 0 si pas encore suivi). Ce semestre influera les
interclassement par semestre dans la promo.
Args:
etudid: L'identifiant d'un étudiant
Elle calcule également sur les aggrégats (cf. attribut self.aggregats):
* pour un semestre de type Si, elle stocke le (ou les) formsemestres de numéro i qu'a suivi un étudiant
(2 si redoublant) dans self.aggregats
* pour des aggrégats de type iS ou iA (par ex, 3A=S1+S2+S3), elle identifie les semestres que l'étudiant
a suivi pour l'amener jusqu'au semestre terminal de l'aggrégat. Ce parcours peut être :
** S1+S2+S1+S2+S3 si redoublement de la 1ère année
** S1+S2+(année de césure)+S3 si césure, ...
Par ex: M. N..z (redoublant en 2ème année) au moment de son 2ème S3 :
{'1A': {26: {18: <FormSemestre 18 BUT Réseaux et Télécommunications semestre 1 FI 2021-2022>,
26: <FormSemestre 26 BUT Réseaux et Télécommunications semestre 2 FI 2022>}},
'2A': {79: {18: <FormSemestre 18 BUT Réseaux et Télécommunications semestre 1 FI 2021-2022>,
26: <FormSemestre 26 BUT Réseaux et Télécommunications semestre 2 FI 2022>,
56: <FormSemestre 56 BUT Réseaux et Télécommunications semestre 3 FI 2022-2023>,
79: <FormSemestre 79 BUT Réseaux et Télécommunications semestre 4 FI 2023>}},
'3A': {},
'3S': {112: {18: <FormSemestre 18 BUT Réseaux et Télécommunications semestre 1 FI 2021-2022>,
26: <FormSemestre 26 BUT Réseaux et Télécommunications semestre 2 FI 2022>,
56: <FormSemestre 56 BUT Réseaux et Télécommunications semestre 3 FI 2022-2023>,
112: <FormSemestre 112 BUT Réseaux et Télécommunications 2023 semestre 3 FI 2023-2024>}},
'4S': {79: {18: <FormSemestre 18 BUT Réseaux et Télécommunications semestre 1 FI 2021-2022>,
26: <FormSemestre 26 BUT Réseaux et Télécommunications semestre 2 FI 2022>,
56: <FormSemestre 56 BUT Réseaux et Télécommunications semestre 3 FI 2022-2023>,
79: <FormSemestre 79 BUT Réseaux et Télécommunications semestre 4 FI 2023>}},
'5S': {},
'6S': {},
'S1': {18: {18: <FormSemestre 18 BUT Réseaux et Télécommunications semestre 1 FI 2021-2022>}},
'S2': {26: {26: <FormSemestre 26 BUT Réseaux et Télécommunications semestre 2 FI 2022>}},
'S3': {112: {56: <FormSemestre 56 BUT Réseaux et Télécommunications semestre 3 FI 2022-2023>,
112: <FormSemestre 112 BUT Réseaux et Télécommunications 2023 semestre 3 FI 2023-2024>}},
'S4': {79: {79: <FormSemestre 79 BUT Réseaux et Télécommunications semestre 4 FI 2023>}},
'S5': {},
'S6': {}
}
Returns:
Un dictionnaire ``{fid: FormSemestre(fid)`` dans lequel les semestres
amènent à une diplomation avant l'annee de diplomation du jury
"""
semestres_etudiant = self.cursus[etudid]["formsemestres"]
"""Ne conserve que les semestres qui l'auraient amené à être diplomé l'année visée"""
semestres_etudiant = self.cursus[etudid]["formsemestres"]
semestres_significatifs = {}
for fid in semestres_etudiant:
semestre = semestres_etudiant[fid]
if pe_tools.get_annee_diplome_semestre(semestre) <= self.annee_diplome:
semestres_significatifs[fid] = semestre
return semestres_significatifs
self.aggregats[etudid] = {}
def structure_cursus_etudiant(self, etudid: int):
"""Structure les informations sur les semestres suivis par un
étudiant, pour identifier les semestres qui seront pris en compte lors de ses calculs
de moyennes PE.
Cette structuration s'appuie sur les numéros de semestre: pour chaque Si, stocke :
le dernier semestre (en date) de numéro i qu'il a suivi (1 ou 0 si pas encore suivi). Ce semestre influera les
interclassement par semestre dans la promo.
"""
semestres_significatifs = self.get_semestres_significatifs(etudid)
"""Tri des semestres par numéro de semestre"""
for nom_sem in pe_tools.TOUS_LES_SEMESTRES:
@ -274,49 +238,37 @@ class EtudiantsJuryPE:
for fid in semestres_significatifs
if semestres_significatifs[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
self.aggregats[etudid][nom_sem] = {}
if dernier_semestre_i:
fid_dernier_semestre_i = list(dernier_semestre_i.keys())[0]
self.aggregats[etudid][nom_sem][fid_dernier_semestre_i] = semestres_i
self.cursus[etudid][nom_sem] = semestres_i
"""Tri des semestres par aggrégat et par semestre terminal"""
for aggregat in pe_tools.TOUS_LES_AGGREGATS:
self.aggregats[etudid][aggregat] = {}
def get_trajectoire(self, etudid: int, formsemestre_final: FormSemestre):
"""Ensemble des semestres parcourus par
un étudiant pour l'amener à un semestre terminal.
"""L'aggrégat considéré (par ex: 3S), son nom de son semestre terminal (par ex: S3) et son numéro (par ex: 3)"""
noms_semestre_de_aggregat = pe_tools.PARCOURS[aggregat]["aggregat"]
nom_semestre_terminal = noms_semestre_de_aggregat[-1]
numero_semestre_terminal = int(nom_semestre_terminal[-1])
Par ex: si formsemestre_terminal est un S3, ensemble des S1,
S2, S3 suivi pour l'amener au S3 (il peut y avoir plusieurs S1,
ou S2, ou S3 s'il a redoublé).
"""Le formsemestre terminal de l'aggrégat (par ex: son dernier S3 en date)"""
dernier_formsemestre_terminal = self.cursus[etudid][nom_semestre_terminal]
Les semestres parcourus sont antérieurs (en terme de date de fin)
au formsemestre_terminal.
# for formsem_id_term in formsemestres_terminal:
if dernier_formsemestre_terminal: # ne considérant que le dernier
formsem_id_term = list(dernier_formsemestre_terminal.keys())[0]
Args:
etudid: L'identifiant de l'étudiant
formsemestre_final: le semestre final visé
"""
numero_semestre_terminal = formsemestre_final.semestre_id
semestres_significatifs = self.get_semestres_significatifs(etudid)
formsemestre_terminal = self.cursus[etudid]["formsemestres"][
formsem_id_term
]
"""Semestres de n° inférieur (pax ex: des S1, S2, S3 pour un S3 terminal) et qui lui sont antérieurs"""
semestres_aggreges = {}
for fid in semestres_significatifs:
semestre = semestres_significatifs[fid]
if (
semestre.semestre_id <= numero_semestre_terminal
and semestre.date_fin <= formsemestre_terminal.date_fin
):
semestres_aggreges[fid] = semestre
self.aggregats[etudid][aggregat][formsem_id_term] = semestres_aggreges
"""Vérifications"""
dernier_semestre_aggregat = get_dernier_semestre(semestres_aggreges)
assert dernier_semestre_aggregat == dernier_formsemestre_terminal
"""Semestres de n° inférieur (pax ex: des S1, S2, S3 pour un S3 terminal) et qui lui sont antérieurs"""
semestres_aggreges = {}
for fid in semestres_significatifs:
semestre = semestres_significatifs[fid]
if (
semestre.semestre_id <= numero_semestre_terminal
and semestre.date_fin <= formsemestre_final.date_fin
):
semestres_aggreges[fid] = semestre
return semestres_aggreges
def get_formsemestres_terminaux_aggregat(self, aggregat: str):
"""Pour un aggrégat donné, ensemble des formsemestres terminaux possibles pour l'aggrégat
@ -333,47 +285,25 @@ class EtudiantsJuryPE:
aggregat: L'aggrégat
Returns:
Un dictionnaire {fid: FormSemestre(fid)}
Un dictionnaire ``{fid: FormSemestre(fid)}``
"""
formsemestres_terminaux = {}
for etudid in self.aggregats:
if self.aggregats[etudid][aggregat]:
print(self.aggregats[etudid][aggregat])
"""Le formsemestre_id du semestre terminal de l'étudiant (s'il existe)"""
fid = list(self.aggregats[etudid][aggregat].keys())[0]
"""Le formsemestre associé (en le prenant dans l'aggrégat)"""
formsemestres_terminaux[fid] = self.aggregats[etudid][aggregat][fid][
fid
]
for etudid in self.trajectoires:
if self.trajectoires[etudid][aggregat]:
trajectoire = self.trajectoires[etudid][aggregat]
"""Le semestre terminal de l'étudiant de l'aggrégat"""
fid = trajectoire.semestre_final.formsemestre_id
formsemestres_terminaux[fid] = trajectoire.semestre_final
return formsemestres_terminaux
def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int):
"""Pour un aggrégat donné associé à un formsemestre terminal cible, renvoie l'ensemble des semestres à
prendre en compte dans l'aggrégat sous la forme d'un dictionnaire {fid: FormSemestre(fid)}.
Fusionne les cursus individuels des étudiants, dont le cursus correspond à l'aggrégat visé.
Args:
aggregat: Un aggrégat (par ex. 1A, 2A, 3S, 6S)
formsemestre_id_terminal: L'identifiant du formsemestre terminal de l'aggrégat, devant correspondre au
dernier semestre de l'aggrégat
"""
noms_semestres_aggreges = pe_tools.PARCOURS[aggregat]["aggregat"]
formsemestres = {}
for etudid in self.cursus:
cursus_etudiant = self.cursus[etudid][aggregat]
if formsemestre_id_terminal in cursus_etudiant:
formsemestres_etudiant = cursus_etudiant[formsemestre_id_terminal]
formsemestres = formsemestres | formsemestres_etudiant
return formsemestres
def get_formsemestres_jury(self, semestres_recherches=None):
def get_formsemestres(self, semestres_recherches=None):
"""Ayant connaissance des étudiants dont il faut calculer les moyennes pour
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``.
le jury PE (attribut `self.etudiant_ids) et de leur cursus (semestres
parcourus),
renvoie un dictionnaire ``{fid: FormSemestre(fid)}``
contenant l'ensemble des formsemestres de leurs 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 :
@ -384,20 +314,20 @@ class EtudiantsJuryPE:
* '3S', '4S' : pour obtenir les combinaisons de semestres définies par les aggrégats
Returns:
Un dictionnaire de la forme {fid: FormSemestre(fid)}
Un dictionnaire de la forme ``{fid: FormSemestre(fid)}``
Remarque:
Une liste de la forme `[ 'Si', 'iA' , ... ]` (combinant les formats précédents) est possible.
Une liste de la forme ``[ 'Si', 'iA' , ... ]`` (combinant les formats précédents) est possible.
"""
if semestres_recherches is None:
"""Appel récursif pour obtenir tous les semestres (validants)"""
semestres = self.get_formsemestres_jury(pe_tools.AGGREGAT_DIPLOMANT)
semestres = self.get_formsemestres(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_elmt = self.get_formsemestres(elmt)
semestres = semestres | semestres_elmt
return semestres
elif (
@ -405,7 +335,7 @@ class EtudiantsJuryPE:
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(
semestres = self.get_formsemestres(
pe_tools.PARCOURS[semestres_recherches]["aggregat"]
)
return semestres
@ -420,24 +350,21 @@ class EtudiantsJuryPE:
nom_sem = semestres_recherches
semestres = {}
for etudid in self.etudiants_ids:
for sem_terminal in self.aggregats[etudid]:
for sem in self.aggregats[etudid][sem_terminal]:
semestres = semestres | self.aggregats[etudid][sem_terminal][sem]
if self.cursus[etudid][nom_sem]:
semestres = semestres | self.cursus[etudid][nom_sem]
return semestres
else:
raise ValueError(
"Probleme de paramètres d'appel dans get_formsemestreids_du_jury"
)
raise ValueError("Probleme de paramètres d'appel dans get_formsemestreids")
def get_etudiants_dans_semestres(semestres: dict[FormSemestre]) -> set:
def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set:
"""Ensemble d'identifiants des étudiants (identifiés via leur ``etudid``)
inscrits à l'un des semestres de la liste de ``semestres``.
Remarque : Les ``cosemestres`` sont généralement obtenus avec ``sco_formsemestre.do_formsemestre_list()``
Args:
semestres: Un dictionnaire {fid: Formsemestre(fid)} donnant un
semestres: Un dictionnaire ``{fid: Formsemestre(fid)}`` donnant un
ensemble d'identifiant de semestres
Returns:
@ -521,7 +448,8 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b
etudid = identite.etudid
"""Son dernier semestre en date"""
dernier_formsemestre = identite.get_formsemestres()[0]
semestres = {sem.semestre_id: sem for sem in identite.get_formsemestres()}
dernier_formsemestre = get_dernier_semestre_en_date(semestres)
numero_dernier_formsemestre = dernier_formsemestre.semestre_id
"""Les numéro de semestres possible dans lesquels il pourrait s'incrire"""
@ -555,27 +483,22 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b
return False
def get_dernier_semestre(semestres: dict[int, FormSemestre]):
"""Renvoie le dernier semestre en date (de fin) d'un dictionnaire
de semestres de la forme {fid: FormSemestre(fid)}.
La date prise en compte est celle marquant la **fin** des semestres.
def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]):
"""Renvoie le dernier semestre en **date de fin** 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
Le FormSemestre du semestre le plus récent
"""
if semestres:
fid_dernier_semestre = list(semestres.keys())[0]
dernier_semestre = {fid_dernier_semestre: semestres[fid_dernier_semestre]}
dernier_semestre: FormSemestre = 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
if semestres[fid].date_fin > dernier_semestre.date_fin:
dernier_semestre = semestres[fid]
return dernier_semestre
else:
return {}
return None

124
app/pe/pe_interclassetag.py Normal file
View File

@ -0,0 +1,124 @@
import pandas as pd
from app.pe import pe_tagtable
from app.pe.pe_tools import PE_DEBUG, pe_print
import app.pe.pe_etudiant as pe_etudiant
from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE
from app.comp import moy_sem
import pandas as pd
import numpy as np
class AggregatInterclasseTag(pe_tagtable.TableTag):
"""Interclasse l'ensemble des étudiants diplômés à une année
donnée (celle du jury), pour un aggrégat donné (par ex: 'S2', '3S')
en reportant :
* les moyennes obtenues sur la trajectoire qu'il ont suivi pour atteindre le numéro de semestre de fin de l'aggrégat (indépendamment de son
formsemestres)
* calculant le classement sur les étudiants diplômes
"""
# -------------------------------------------------------------------------------------------------------------------
def __init__(
self,
nom_aggregat: str,
etudiants: EtudiantsJuryPE,
trajectoires_jury_pe: TrajectoiresJuryPE,
trajectoires_taggues: dict[tuple, TrajectoireTag],
):
""""""
"""Table nommée au nom de l'aggrégat (par ex: 3S"""
pe_tagtable.TableTag.__init__(self, nom_aggregat)
"""Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)"""
self.diplomes_ids = etudiants.diplomes_ids
self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids}
"""Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat
"""
self.trajectoires: dict[int, Trajectoire] = {}
for trajectoire_id in trajectoires_jury_pe.trajectoires:
trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id]
if trajectoire_id[0] == nom_aggregat:
self.trajectoires[trajectoire_id] = trajectoire
self.trajectoires_taggues: dict[int, Trajectoire] = {}
for trajectoire_id in self.trajectoires:
self.trajectoires_taggues[trajectoire_id] = trajectoires_taggues[
trajectoire_id
]
"""Les trajectoires suivies par les étudiants du jury, en ne gardant que
celles associées aux diplomés"""
self.suivis: dict[int, Trajectoire] = {}
for etudid in self.diplomes_ids:
self.suivis[etudid] = trajectoires_jury_pe.suivis[etudid][nom_aggregat]
"""Les tags"""
self.tags_sorted = self.do_taglist()
"""Construit la matrice de notes"""
self.notes = self.compute_notes_matrice()
"""Synthétise les moyennes/classements par tag"""
self.moyennes_tags = {}
for tag in self.tags_sorted:
moy_gen_tag = self.notes[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),
}
def do_taglist(self):
"""Synthétise les tags à partir des trajectoires_tagguées
Returns:
Une liste de tags triés par ordre alphabétique
"""
tags = []
for trajectoire_id in self.trajectoires_taggues:
trajectoire = self.trajectoires_taggues[trajectoire_id]
tags.extend(trajectoire.tags_sorted)
return sorted(set(tags))
def get_etudids(self):
return list(self.etudiants_diplomes.keys())
def compute_notes_matrice(self):
"""Construit la matrice de notes (etudid x tags)
retraçant les moyennes obtenues par les étudiants dans les semestres associés à
l'aggrégat (une trajectoire ayant pour numéro de semestre final, celui de l'aggrégat).
"""
nb_tags = len(self.tags_sorted)
nb_etudiants = len(self.diplomes_ids)
"""Index de la matrice (etudids -> dim 0, tags -> dim 1)"""
etudids = [etud.etudid for etud in self.diplomes_ids]
tags = self.tags_sorted
"""Partant d'un dataframe vierge"""
df = pd.DataFrame(np.nan, index=etudids, columns=tags)
for trajectoire_id in self.trajectoires_taggues:
"""Charge les moyennes par tag de la trajectoire tagguée"""
notes = self.trajectoires_taggues[trajectoire_id].notes
"""Etudiants/Tags communs entre la trajectoire_tagguée et les données interclassées"""
etudids_communs = df.index.intersection(notes.index)
tags_communs = df.columns.intersection(notes.columns)
"""Injecte les notes par tag"""
df.loc[etudids_communs, tags_communs] = notes.loc[
etudids_communs, tags_communs
]
return df

View File

@ -68,8 +68,9 @@ from app.scodoc import sco_formsemestre
from app.pe import pe_tagtable
from app.pe import pe_tools
from app.pe import pe_semestretag
from app.pe import pe_settag
from app.pe import pe_trajectoiretag
from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import TrajectoiresJuryPE
# ----------------------------------------------------------------------------------------
@ -154,17 +155,24 @@ class JuryPE(object):
filename, formsemestretag.str_tagtable(), path="details_semestres"
)
"""Génère les trajectoires (combinaison de semestres suivis
par un étudiant pour atteindre le semestre final d'un aggrégat)
"""
self.trajectoires = TrajectoiresJuryPE(self.diplome)
self.trajectoires.cree_trajectoires(self.etudiants)
"""Génère les aggrégats de semestre (par ex: 1A, 3S, 5S) avec calcul
des moyennes pour le jury"""
self.aggregats_taggues = compute_aggregats_tag(
self.trajectoires_tagguees = compute_trajectoires_tag(
self.etudiants, self.semestres_taggues
)
if pe_tools.PE_DEBUG:
"""Intègre le bilan des aggrégats de semestres au zip final"""
for aggregat in self.aggregats_taggues:
for fid in self.aggregats_taggues[aggregat]:
set_tag = self.aggregats_taggues[aggregat][fid]
for aggregat in self.trajectoires_tagguees:
for fid in self.trajectoires_tagguees[aggregat]:
set_tag = self.trajectoires_tagguees[aggregat][fid]
filename = set_tag.nom.replace(" ", "_") + ".csv"
pe_tools.pe_print(f" - Export csv de {filename} ")
self.add_file_to_zip(
@ -174,7 +182,7 @@ class JuryPE(object):
"""Génère les interclassements par (nom d') aggrégat"""
self.aggregats_taggues_interclasses = compute_interclassements(
self.etudiants, #
self.aggregats_taggues,
self.trajectoires_tagguees,
)
# Les interclassements
# --------------------
@ -276,7 +284,7 @@ class JuryPE(object):
self.etudiants.cursus[etudid][nom]
]
else:
tagtable = self.aggregats_taggues[nom][
tagtable = self.trajectoires_tagguees[nom][
self.etudiants.cursus[etudid][nom]
]
for tag in tagtable.get_all_tags():
@ -630,7 +638,7 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE):
"""Création des semestres taggués, de type 'S1', 'S2', ..."""
pe_tools.pe_print("*** Création des semestres taggués")
formsemestres = etudiants.get_formsemestres_jury(
formsemestres = etudiants.get_formsemestres(
semestres_recherches=pe_tools.TOUS_LES_SEMESTRES
)
@ -655,14 +663,18 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE):
return semestres_tags
def compute_aggregats_tag(etudiants: EtudiantsJuryPE, semestres_tag: dict[SemestreTag]):
"""Créé les combinaisons de semestres (aggrégats), en calculant les moyennes et les
classements par tag pour chacune. Chaque combinaison (aggrégat) est identifiée
par un formsemestre terminal.
def compute_trajectoires_tag(trajectoires: TrajectoiresJuryPE,
etudiants: EtudiantsJuryPE,
semestres_taggues: dict[int, SemestreTag]):
"""Créée les trajectoires tagguées (combinaison aggrégeant plusieurs semestres au sens
d'un aggrégat (par ex: '3S')),
en calculant les moyennes et les classements par tag pour chacune.
Pour rappel : Chaque trajectoire est identifiée un nom d'aggrégat et par un formsemestre terminal.
Par exemple :
* combinaisons '3S' : S1+S2+S3 en prenant en compte tous les S3 qu'ont fréquentés les
* combinaisons '3S' : S1+S2+S3 en prenant en compte tous les S3 qu'ont fréquenté les
étudiants du jury PE. Ces S3 marquent les formsemestre terminal de chaque combinaison.
* combinaisons 'S2' : 1 seul S2 pour des étudiants n'ayant pas redoublé, 2 pour des redoublants (dont les
@ -680,53 +692,24 @@ def compute_aggregats_tag(etudiants: EtudiantsJuryPE, semestres_tag: dict[Semest
pe_tools.pe_print(" *** Création des aggrégats ")
sets_tags = {}
trajectoires_tagguees = {}
for aggregat in pe_tools.TOUS_LES_SEMESTRES + pe_tools.TOUS_LES_AGGREGATS:
sets_tags[aggregat] = {}
for trajectoire_id in trajectoires_tagguees:
trajectoire = trajectoires[trajectoire_id]
nom = trajectoire.get_repr()
"""Semestres aggrégés"""
if aggregat in pe_tools.TOUS_LES_SEMESTRES: # par ex. 'S2'
noms_semestres_aggreges = [aggregat]
else: # par ex. "5S"
noms_semestres_aggreges = pe_tools.PARCOURS[aggregat]["aggregat"]
nom_semestre_terminal = noms_semestres_aggreges[-1]
pe_tools.pe_print(f" --> Fusion {nom}")
pe_tools.pe_print(f"* {aggregat}: " + "+".join(noms_semestres_aggreges))
"""Les formsemestres terminaux des aggrégats"""
formsemestres_terminal = etudiants.get_formsemestres_terminaux_aggregat(
aggregat
)
for frmsem_id in formsemestres_terminal:
formsemestre_terminal = formsemestres_terminal[frmsem_id]
"""Nom du set_tag"""
nom = "Aggrégat %s %d %s %d-%d" % (
aggregat,
frmsem_id,
"+".join(noms_semestres_aggreges),
formsemestre_terminal.date_debut.year,
formsemestre_terminal.date_fin.year,
"""Création de la trajectoire_tagguee associée"""
trajectoire_tagguee = pe_trajectoiretag.TrajectoireTag(
nom, trajectoire, semestres_taggues, etudiants
)
"""Semestres à aggreger dans l'aggrégat ayant amené des étudiants jusqu'au formsemestre_terminal"""
semestres_aggreges = etudiants.get_semestres_a_aggreger(aggregat, frmsem_id)
"""Mémorise le résultat"""
trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee
pe_tools.pe_print(" --> Fusion de :")
for fid in semestres_aggreges:
pe_tools.pe_print(str(semestres_aggreges[fid]))
return trajectoires_tagguees
"""Création du settag associé"""
settag = pe_settag.SetTag(
nom, formsemestre_terminal, semestres_aggreges, semestres_tag, etudiants
)
settag.compute_notes_cube() # Calcul les moyennes, les rangs, ..
sets_tags[aggregat][fid] = settag # Mémorise le résultat
return sets_tags
def compute_interclassements(

View File

@ -1,7 +1,7 @@
from app.pe import pe_tagtable
from app.pe.pe_tools import PE_DEBUG, pe_print
import app.pe.pe_etudiant as pe_etudiant
from app.pe.pe_etudiant import EtudiantsJuryPE
class SetTagInterClasse(pe_tagtable.TableTag):
"""Interclasse les étudiants d'une promo (ceux diplômé) par aggrégat de même nom

149
app/pe/pe_trajectoire.py Normal file
View File

@ -0,0 +1,149 @@
import app.pe.pe_tools as pe_tools
from app.models import FormSemestre
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
class Trajectoire:
"""Modélise, pour un aggrégat visé (par ex: 'S2', '3S', '2A')
et un ensemble d'étudiants donnés,
la combinaison des formsemestres des étudiants amenant à un semestre
terminal visé.
Si l'aggrégat est un semestre de type Si, elle stocke le (ou les)
formsemestres de numéro i qu'ont suivis l'étudiant pour atteindre le Si
(en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants)
Pour des aggrégats de type iS ou iA (par ex, 3A=S1+S2+S3), elle identifie
les semestres que les étudiants ont suivis pour les amener jusqu'au semestre
terminal de la trajectoire (par ex: ici un S3).
Ces semestres peuvent être :
* des S1+S2+S1+S2+S3 si redoublement de la 1ère année
* des S1+S2+(année de césure)+S3 si césure, ...
"""
def __init__(self, nom_aggregat: str, semestre_final: FormSemestre):
"""Modélise un ensemble de formsemestres d'étudiants
amenant à un semestre terminal
Args:
nom_aggregat: Un nom d'aggrégat (par ex: '5S')
semestre_final: Le semestre final de l'aggrégat
"""
self.nom = nom_aggregat
self.semestre_final = semestre_final
self.trajectoire_id = (nom_aggregat, semestre_final.formsemestre_id)
"""Les semestres à aggréger"""
self.semestres_aggreges = {}
def add_semestres_a_aggreger(self, semestres: dict[int: FormSemestre]):
"""Ajoute des semestres au semestre à aggréger
Args:
semestres: Dictionnaire ``{fid: FormSemestre(fid)} à ajouter``
"""
self.semestres_aggreges = self.semestres_aggreges | semestres
def get_repr(self):
"""Représentation textuelle d'une trajectoire
basée sur ses semestres aggrégés"""
noms = []
for fid in self.semestres_aggreges:
semestre = self.semestres_aggreges[fid]
noms.append(f"S{semestre.semestre_id}({fid})")
noms = sorted(noms)
repr = f"{self.nom} ({self.semestre_final.formsemestre_id}) {self.semestre_final.date_fin.year}"
if noms:
repr += " - " + "+".join(noms)
return repr
class TrajectoiresJuryPE:
"""Centralise toutes les trajectoires du jury PE"""
def __init__(self, annee_diplome: int):
"""
Args:
annee_diplome: L'année de diplomation
"""
self.annee_diplome = annee_diplome
"""Toutes les trajectoires possibles"""
self.trajectoires: dict[tuple: Trajectoire] = {}
"""Quelle trajectoires pour quel étudiant :
dictionnaire {etudid: {nom_aggregat: Trajectoire}}"""
self.suivi: dict[int: str] = {}
def cree_trajectoires(self, etudiants: EtudiantsJuryPE):
"""Créé toutes les trajectoires, au regard du cursus des étudiants
analysés + les mémorise dans les données de l'étudiant
"""
for nom_aggregat in pe_tools.TOUS_LES_SEMESTRES + pe_tools.TOUS_LES_AGGREGATS:
"""L'aggrégat considéré (par ex: 3S=S1+S2+S3), son nom de son semestre terminal (par ex: S3) et son numéro (par ex: 3)"""
noms_semestre_de_aggregat = pe_tools.PARCOURS[nom_aggregat]["aggregat"]
nom_semestre_terminal = noms_semestre_de_aggregat[-1]
for etudid in etudiants.cursus:
if etudid not in self.suivi:
self.suivi[etudid] = {}
"""Le formsemestre terminal (dernier en date) associé au
semestre marquant la fin de l'aggrégat
(par ex: son dernier S3 en date)"""
semestres = etudiants.cursus[etudid][nom_semestre_terminal]
if semestres:
formsemestre_final = get_dernier_semestre_en_date(semestres)
"""Ajout ou récupération de la trajectoire"""
trajectoire_id = (nom_aggregat, formsemestre_final.formsemestre_id)
if trajectoire_id not in self.trajectoires:
trajectoire = Trajectoire(nom_aggregat, formsemestre_final)
self.trajectoires[trajectoire_id] = trajectoire
else:
trajectoire = self.trajectoires[trajectoire_id]
"""La liste des semestres de l'étudiant à prendre en compte
pour cette trajectoire"""
semestres_a_aggreger = etudiants.get_trajectoire(etudid, formsemestre_final)
"""Ajout des semestres à la trajectoire"""
trajectoire.add_semestres_a_aggreger(semestres_a_aggreger)
"""Mémoire la trajectoire suivie par l'étudiant"""
self.suivi[etudid][nom_aggregat] = trajectoire
"""Vérifications"""
# dernier_semestre_aggregat = get_dernier_semestre_en_date(semestres_aggreges)
# assert dernier_semestre_aggregat == formsemestre_terminal
def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int):
"""Pour un nom d'aggrégat donné (par ex: 'S3') et un semestre terminal cible
identifié par son formsemestre_id (par ex: 'S3 2022-2023'),
renvoie l'ensemble des semestres à prendre en compte dans
l'aggrégat sous la forme d'un dictionnaire {fid: FormSemestre(fid)}.
Fusionne les cursus individuels des étudiants, dont le cursus correspond
à l'aggrégat visé.
Args:
aggregat: Un aggrégat (par ex. 1A, 2A, 3S, 6S)
formsemestre_id_terminal: L'identifiant du formsemestre terminal de l'aggrégat, devant correspondre au
dernier semestre de l'aggrégat
"""
noms_semestres_aggreges = pe_tools.PARCOURS[aggregat]["aggregat"]
formsemestres = {}
for etudid in self.cursus:
cursus_etudiant = self.cursus[etudid][aggregat]
if formsemestre_id_terminal in cursus_etudiant:
formsemestres_etudiant = cursus_etudiant[formsemestre_id_terminal]
formsemestres = formsemestres | formsemestres_etudiant
return formsemestres

View File

@ -35,6 +35,7 @@ Created on Fri Sep 9 09:15:05 2016
@author: barasc
"""
from app.comp import moy_sem
from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre
@ -42,35 +43,43 @@ from app.pe.pe_semestretag import SemestreTag
from app.pe import pe_tagtable
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_trajectoire import TrajectoiresJuryPE
class SetTag(pe_tagtable.TableTag):
"""Agrège plusieurs semestres (ou settag) taggués (SemestreTag/Settag de 1 à 4) pour extraire des moyennes
et des classements par tag pour un groupe d'étudiants donnés.
par. exemple fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S'
Le settag est identifié sur la base du dernier semestre (ici le 'S3') ;
les étudiants considérés sont donc ceux inscrits dans ce S3
à condition qu'ils disposent d'un parcours sur tous les semestres fusionnés valides (par. ex
un etudiant non inscrit dans un S1 mais dans un S2 et un S3 n'est pas pris en compte).
class TrajectoireTag(pe_tagtable.TableTag):
"""Calcule les moyennes par tag d'une combinaison de semestres
(trajectoires), identifiée par un nom d'aggrégat (par ex: '3S') et
par un semestre terminal, pour extraire les classements par tag pour un
groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous
participé au semestre terminal.
Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S'
"""
# -------------------------------------------------------------------------------------------------------------------
def __init__(
self,
nom,
formsemestre_terminal: FormSemestre,
semestres_aggreges: dict[int, FormSemestre],
nom: str,
trajectoire: Trajectoire,
semestres_taggues: dict[int, SemestreTag],
donnees_etudiants: EtudiantsJuryPE,
):
pe_tagtable.TableTag.__init__(self, nom)
""" """
pe_tagtable.TableTag.__init__(self, nom=nom)
"""La trajectoire associée"""
self.trajectoire_id = trajectoire.trajectoire_id
self.trajectoire = trajectoire
"""Le formsemestre terminal et les semestres aggrégés"""
self.formsemestre_terminal = formsemestre_terminal
nt = load_formsemestre_results(formsemestre_terminal)
self.semestres_aggreges = semestres_aggreges
self.formsemestre_terminal = trajectoire.semestre_final
nt = load_formsemestre_results(self.formsemestre_terminal)
self.semestres_aggreges = trajectoire.semestres_aggreges
"""Les semestres tags associés aux semestres aggrégés"""
try:
@ -83,6 +92,7 @@ class SetTag(pe_tagtable.TableTag):
"""Les étudiants (état civil + cursus connu)"""
self.etuds = nt.etuds
# assert self.etuds == trajectoire.suivi # manque-t-il des étudiants ?
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
self.cursus = {
etudid: donnees_etudiants.cursus[etudid] for etudid in self.etudiants
@ -155,7 +165,6 @@ class SetTag(pe_tagtable.TableTag):
def get_etudids(self):
return list(self.etudiants.keys())
# -------------------------------------------------------------------------------------------------------------------
def do_taglist(self):
"""Synthétise les tags à partir des semestres (taggués) aggrégés