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 import app.pe.pe_tools as pe_tools
from app.models import FormSemestre, Identite from app.models import FormSemestre, Identite
from app.pe.pe_tools import pe_print 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: class EtudiantsJuryPE:
"""Classe centralisant la gestion des étudiants à prendre en compte dans un jury de PE""" """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" "Les identités des étudiants traités pour le jury"
self.identites = {} # ex. ETUDINFO_DICT self.identites = {} # ex. ETUDINFO_DICT
"Les cursus (semestres suivis, abandons, dernier S1, S2, ...) des étudiants" "Les cursus (semestres suivis, abandons) des étudiants"
self.cursus = {} self.cursus = {}
"""Les aggrégats des semestres suivis (par ex: 3S=S1+S2+S3 à prendre en compte avec d'éventuels redoublements) des étudiants""" """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)" "Les etudids des étudiants à considérer au jury (ceux qui seront effectivement diplômés)"
self.diplomes_ids = {} self.diplomes_ids = {}
"Les etudids des étudiants dont il faut calculer les moyennes/classements (même si d'éventuels abandons)" "Les etudids des étudiants dont il faut calculer les moyennes/classements (même si d'éventuels abandons)"
self.etudiants_ids = {} self.etudiants_ids = {}
"Les formsemestres dont il faut calculer les moyennes par tag"
self.formsemestres_jury_ids = {}
def find_etudiants(self, formation_id: int): def find_etudiants(self, formation_id: int):
"""Liste des étudiants à prendre en compte dans le jury PE, en les recherchant """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`` 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) self.analyse_etat_etudiant(etudid, cosemestres)
"""L'analyse de son parcours pour atteindre chaque semestre de la formation""" """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: if (no_etud + 1) % 10 == 0:
pe_tools.pe_print((no_etud + 1), " ", end="") pe_tools.pe_print((no_etud + 1), " ", end="")
@ -130,7 +118,7 @@ class EtudiantsJuryPE:
self.etudiants_ids = {etudid for etudid in self.identites} self.etudiants_ids = {etudid for etudid in self.identites}
"""Les formsemestres (des étudiants) dont il faut calculer les moyennes""" """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 # Synthèse
pe_tools.pe_print( 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 ?""" """ 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)
def analyse_parcours_etudiant_dans_semestres(self, etudid: int): def get_semestres_significatifs(self, etudid: int):
"""Structure les informations sur les semestres suivis par un """Ensemble des semestres d'un étudiant, qui l'auraient amené à être diplomé
étudiant, pour identifier les semestres qui seront pris en compte lors de ses calculs l'année visée (supprime les semestres qui conduisent à une diplomation
de moyennes PE. postérieure à celle du jury visé)
Cette structuration (cf. attribut self.cursus) s'appuie sur les numéros de semestre: pour chaque Si, stocke : Args:
le dernier semestre (en date) de numéro i qu'il a suivi (1 ou 0 si pas encore suivi). Ce semestre influera les etudid: L'identifiant d'un étudiant
interclassement par semestre dans la promo.
Elle calcule également sur les aggrégats (cf. attribut self.aggregats): Returns:
Un dictionnaire ``{fid: FormSemestre(fid)`` dans lequel les semestres
* pour un semestre de type Si, elle stocke le (ou les) formsemestres de numéro i qu'a suivi un étudiant amènent à une diplomation avant l'annee de diplomation du jury
(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': {}
}
""" """
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 = {} semestres_significatifs = {}
for fid in semestres_etudiant: for fid in semestres_etudiant:
semestre = semestres_etudiant[fid] semestre = semestres_etudiant[fid]
if pe_tools.get_annee_diplome_semestre(semestre) <= self.annee_diplome: if pe_tools.get_annee_diplome_semestre(semestre) <= self.annee_diplome:
semestres_significatifs[fid] = semestre 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""" """Tri des semestres par numéro de semestre"""
for nom_sem in pe_tools.TOUS_LES_SEMESTRES: for nom_sem in pe_tools.TOUS_LES_SEMESTRES:
@ -274,49 +238,37 @@ class EtudiantsJuryPE:
for fid in semestres_significatifs for fid in semestres_significatifs
if semestres_significatifs[fid].semestre_id == i if semestres_significatifs[fid].semestre_id == i
} # les semestres de n°i de l'étudiant } # les semestres de n°i de l'étudiant
dernier_semestre_i = get_dernier_semestre(semestres_i) self.cursus[etudid][nom_sem] = 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
"""Tri des semestres par aggrégat et par semestre terminal""" def get_trajectoire(self, etudid: int, formsemestre_final: FormSemestre):
for aggregat in pe_tools.TOUS_LES_AGGREGATS: """Ensemble des semestres parcourus par
self.aggregats[etudid][aggregat] = {} 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)""" Par ex: si formsemestre_terminal est un S3, ensemble des S1,
noms_semestre_de_aggregat = pe_tools.PARCOURS[aggregat]["aggregat"] S2, S3 suivi pour l'amener au S3 (il peut y avoir plusieurs S1,
nom_semestre_terminal = noms_semestre_de_aggregat[-1] ou S2, ou S3 s'il a redoublé).
numero_semestre_terminal = int(nom_semestre_terminal[-1])
"""Le formsemestre terminal de l'aggrégat (par ex: son dernier S3 en date)""" Les semestres parcourus sont antérieurs (en terme de date de fin)
dernier_formsemestre_terminal = self.cursus[etudid][nom_semestre_terminal] au formsemestre_terminal.
# for formsem_id_term in formsemestres_terminal: Args:
if dernier_formsemestre_terminal: # ne considérant que le dernier etudid: L'identifiant de l'étudiant
formsem_id_term = list(dernier_formsemestre_terminal.keys())[0] 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"][ """Semestres de n° inférieur (pax ex: des S1, S2, S3 pour un S3 terminal) et qui lui sont antérieurs"""
formsem_id_term semestres_aggreges = {}
] for fid in semestres_significatifs:
semestre = semestres_significatifs[fid]
"""Semestres de n° inférieur (pax ex: des S1, S2, S3 pour un S3 terminal) et qui lui sont antérieurs""" if (
semestres_aggreges = {} semestre.semestre_id <= numero_semestre_terminal
for fid in semestres_significatifs: and semestre.date_fin <= formsemestre_final.date_fin
semestre = semestres_significatifs[fid] ):
if ( semestres_aggreges[fid] = semestre
semestre.semestre_id <= numero_semestre_terminal return semestres_aggreges
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
def get_formsemestres_terminaux_aggregat(self, aggregat: str): def get_formsemestres_terminaux_aggregat(self, aggregat: str):
"""Pour un aggrégat donné, ensemble des formsemestres terminaux possibles pour l'aggrégat """Pour un aggrégat donné, ensemble des formsemestres terminaux possibles pour l'aggrégat
@ -333,47 +285,25 @@ class EtudiantsJuryPE:
aggregat: L'aggrégat aggregat: L'aggrégat
Returns: Returns:
Un dictionnaire {fid: FormSemestre(fid)} Un dictionnaire ``{fid: FormSemestre(fid)}``
""" """
formsemestres_terminaux = {} formsemestres_terminaux = {}
for etudid in self.aggregats: for etudid in self.trajectoires:
if self.aggregats[etudid][aggregat]: if self.trajectoires[etudid][aggregat]:
print(self.aggregats[etudid][aggregat]) trajectoire = self.trajectoires[etudid][aggregat]
"""Le formsemestre_id du semestre terminal de l'étudiant (s'il existe)""" """Le semestre terminal de l'étudiant de l'aggrégat"""
fid = list(self.aggregats[etudid][aggregat].keys())[0] fid = trajectoire.semestre_final.formsemestre_id
"""Le formsemestre associé (en le prenant dans l'aggrégat)""" formsemestres_terminaux[fid] = trajectoire.semestre_final
formsemestres_terminaux[fid] = self.aggregats[etudid][aggregat][fid][
fid
]
return formsemestres_terminaux return formsemestres_terminaux
def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int): def get_formsemestres(self, semestres_recherches=None):
"""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):
"""Ayant connaissance des étudiants dont il faut calculer les moyennes pour """Ayant connaissance des étudiants dont il faut calculer les moyennes pour
le jury PE (attribut `self.etudiant_ids) et de leur cursus, le jury PE (attribut `self.etudiant_ids) et de leur cursus (semestres
renvoie un dictionnaire `{fid: FormSemestre(fid)}` parcourus),
contenant l'ensemble des formsemestres de leur cursus, dont il faudra calculer renvoie un dictionnaire ``{fid: FormSemestre(fid)}``
la moyenne. Les formsemestres sont limités à ceux indiqués dans ``semestres_recherches``. 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: Args:
semestres_recherches: Une liste ou une chaine de caractères parmi : 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 * '3S', '4S' : pour obtenir les combinaisons de semestres définies par les aggrégats
Returns: Returns:
Un dictionnaire de la forme {fid: FormSemestre(fid)} Un dictionnaire de la forme ``{fid: FormSemestre(fid)}``
Remarque: 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: if semestres_recherches is None:
"""Appel récursif pour obtenir tous les semestres (validants)""" """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 return semestres
elif isinstance(semestres_recherches, list): elif isinstance(semestres_recherches, list):
"""Appel récursif sur tous les éléments de la liste""" """Appel récursif sur tous les éléments de la liste"""
semestres = {} semestres = {}
for elmt in semestres_recherches: for elmt in semestres_recherches:
semestres_elmt = self.get_formsemestres_jury(elmt) semestres_elmt = self.get_formsemestres(elmt)
semestres = semestres | semestres_elmt semestres = semestres | semestres_elmt
return semestres return semestres
elif ( elif (
@ -405,7 +335,7 @@ class EtudiantsJuryPE:
and semestres_recherches in pe_tools.TOUS_LES_AGGREGATS 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""" """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"] pe_tools.PARCOURS[semestres_recherches]["aggregat"]
) )
return semestres return semestres
@ -420,24 +350,21 @@ class EtudiantsJuryPE:
nom_sem = semestres_recherches nom_sem = semestres_recherches
semestres = {} semestres = {}
for etudid in self.etudiants_ids: for etudid in self.etudiants_ids:
for sem_terminal in self.aggregats[etudid]: if self.cursus[etudid][nom_sem]:
for sem in self.aggregats[etudid][sem_terminal]: semestres = semestres | self.cursus[etudid][nom_sem]
semestres = semestres | self.aggregats[etudid][sem_terminal][sem]
return semestres return semestres
else: else:
raise ValueError( raise ValueError("Probleme de paramètres d'appel dans get_formsemestreids")
"Probleme de paramètres d'appel dans get_formsemestreids_du_jury"
)
def get_etudiants_dans_semestres(semestres: dict[FormSemestre]) -> set: def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set:
"""Ensemble d'identifiants des étudiants (identifiés via leur ``etudid``) """Ensemble d'identifiants des étudiants (identifiés via leur ``etudid``)
inscrits à l'un des semestres de la liste de ``semestres``. inscrits à l'un des semestres de la liste de ``semestres``.
Remarque : Les ``cosemestres`` sont généralement obtenus avec ``sco_formsemestre.do_formsemestre_list()`` Remarque : Les ``cosemestres`` sont généralement obtenus avec ``sco_formsemestre.do_formsemestre_list()``
Args: Args:
semestres: Un dictionnaire {fid: Formsemestre(fid)} donnant un semestres: Un dictionnaire ``{fid: Formsemestre(fid)}`` donnant un
ensemble d'identifiant de semestres ensemble d'identifiant de semestres
Returns: Returns:
@ -521,7 +448,8 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b
etudid = identite.etudid etudid = identite.etudid
"""Son dernier semestre en date""" """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 numero_dernier_formsemestre = dernier_formsemestre.semestre_id
"""Les numéro de semestres possible dans lesquels il pourrait s'incrire""" """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 return False
def get_dernier_semestre(semestres: dict[int, FormSemestre]): def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]):
"""Renvoie le dernier semestre en date (de fin) d'un dictionnaire """Renvoie le dernier semestre en **date de fin** d'un dictionnaire
de semestres de la forme {fid: FormSemestre(fid)}. de semestres de la forme ``{fid: FormSemestre(fid)}``.
La date prise en compte est celle marquant la **fin** des semestres.
Args: Args:
semestres: Un dictionnaire de semestres semestres: Un dictionnaire de semestres
Return: Return:
Un dictionnaire {fid: FormSemestre(fid)} contenant le semestre le plus récent Le FormSemestre du semestre le plus récent
""" """
if semestres: if semestres:
fid_dernier_semestre = list(semestres.keys())[0] 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: for fid in semestres:
if ( if semestres[fid].date_fin > dernier_semestre.date_fin:
semestres[fid].date_fin dernier_semestre = semestres[fid]
> dernier_semestre[fid_dernier_semestre].date_fin
):
dernier_semestre = {fid: semestres[fid]}
fid_dernier_semestre = fid
return dernier_semestre return dernier_semestre
else: 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_tagtable
from app.pe import pe_tools from app.pe import pe_tools
from app.pe import pe_semestretag 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_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import TrajectoiresJuryPE
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
@ -154,17 +155,24 @@ class JuryPE(object):
filename, formsemestretag.str_tagtable(), path="details_semestres" 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 """Génère les aggrégats de semestre (par ex: 1A, 3S, 5S) avec calcul
des moyennes pour le jury""" des moyennes pour le jury"""
self.aggregats_taggues = compute_aggregats_tag( self.trajectoires_tagguees = compute_trajectoires_tag(
self.etudiants, self.semestres_taggues self.etudiants, self.semestres_taggues
) )
if pe_tools.PE_DEBUG: if pe_tools.PE_DEBUG:
"""Intègre le bilan des aggrégats de semestres au zip final""" """Intègre le bilan des aggrégats de semestres au zip final"""
for aggregat in self.aggregats_taggues: for aggregat in self.trajectoires_tagguees:
for fid in self.aggregats_taggues[aggregat]: for fid in self.trajectoires_tagguees[aggregat]:
set_tag = self.aggregats_taggues[aggregat][fid] set_tag = self.trajectoires_tagguees[aggregat][fid]
filename = set_tag.nom.replace(" ", "_") + ".csv" filename = set_tag.nom.replace(" ", "_") + ".csv"
pe_tools.pe_print(f" - Export csv de {filename} ") pe_tools.pe_print(f" - Export csv de {filename} ")
self.add_file_to_zip( self.add_file_to_zip(
@ -174,7 +182,7 @@ class JuryPE(object):
"""Génère les interclassements par (nom d') aggrégat""" """Génère les interclassements par (nom d') aggrégat"""
self.aggregats_taggues_interclasses = compute_interclassements( self.aggregats_taggues_interclasses = compute_interclassements(
self.etudiants, # self.etudiants, #
self.aggregats_taggues, self.trajectoires_tagguees,
) )
# Les interclassements # Les interclassements
# -------------------- # --------------------
@ -276,7 +284,7 @@ class JuryPE(object):
self.etudiants.cursus[etudid][nom] self.etudiants.cursus[etudid][nom]
] ]
else: else:
tagtable = self.aggregats_taggues[nom][ tagtable = self.trajectoires_tagguees[nom][
self.etudiants.cursus[etudid][nom] self.etudiants.cursus[etudid][nom]
] ]
for tag in tagtable.get_all_tags(): 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', ...""" """Création des semestres taggués, de type 'S1', 'S2', ..."""
pe_tools.pe_print("*** Création des semestres taggués") 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 semestres_recherches=pe_tools.TOUS_LES_SEMESTRES
) )
@ -655,14 +663,18 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE):
return semestres_tags return semestres_tags
def compute_aggregats_tag(etudiants: EtudiantsJuryPE, semestres_tag: dict[SemestreTag]): def compute_trajectoires_tag(trajectoires: TrajectoiresJuryPE,
"""Créé les combinaisons de semestres (aggrégats), en calculant les moyennes et les etudiants: EtudiantsJuryPE,
classements par tag pour chacune. Chaque combinaison (aggrégat) est identifiée semestres_taggues: dict[int, SemestreTag]):
par un formsemestre terminal. """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 : 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. é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 * 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 ") 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: for trajectoire_id in trajectoires_tagguees:
sets_tags[aggregat] = {} trajectoire = trajectoires[trajectoire_id]
nom = trajectoire.get_repr()
"""Semestres aggrégés""" pe_tools.pe_print(f" --> Fusion {nom}")
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"* {aggregat}: " + "+".join(noms_semestres_aggreges)) """Création de la trajectoire_tagguee associée"""
trajectoire_tagguee = pe_trajectoiretag.TrajectoireTag(
"""Les formsemestres terminaux des aggrégats""" nom, trajectoire, semestres_taggues, etudiants
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,
) )
"""Semestres à aggreger dans l'aggrégat ayant amené des étudiants jusqu'au formsemestre_terminal""" """Mémorise le résultat"""
semestres_aggreges = etudiants.get_semestres_a_aggreger(aggregat, frmsem_id) trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee
pe_tools.pe_print(" --> Fusion de :") return trajectoires_tagguees
for fid in semestres_aggreges:
pe_tools.pe_print(str(semestres_aggreges[fid]))
"""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( def compute_interclassements(

View File

@ -1,7 +1,7 @@
from app.pe import pe_tagtable from app.pe import pe_tagtable
from app.pe.pe_tools import PE_DEBUG, pe_print from app.pe.pe_tools import PE_DEBUG, pe_print
import app.pe.pe_etudiant as pe_etudiant import app.pe.pe_etudiant as pe_etudiant
from app.pe.pe_etudiant import EtudiantsJuryPE
class SetTagInterClasse(pe_tagtable.TableTag): class SetTagInterClasse(pe_tagtable.TableTag):
"""Interclasse les étudiants d'une promo (ceux diplômé) par aggrégat de même nom """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 @author: barasc
""" """
from app.comp import moy_sem from app.comp import moy_sem
from app.comp.res_sem import load_formsemestre_results from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre from app.models import FormSemestre
@ -42,35 +43,43 @@ from app.pe.pe_semestretag import SemestreTag
from app.pe import pe_tagtable from app.pe import pe_tagtable
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from app.pe.pe_trajectoire import Trajectoire
from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import TrajectoiresJuryPE
class SetTag(pe_tagtable.TableTag): class TrajectoireTag(pe_tagtable.TableTag):
"""Agrège plusieurs semestres (ou settag) taggués (SemestreTag/Settag de 1 à 4) pour extraire des moyennes """Calcule les moyennes par tag d'une combinaison de semestres
et des classements par tag pour un groupe d'étudiants donnés. (trajectoires), identifiée par un nom d'aggrégat (par ex: '3S') et
par. exemple fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S' par un semestre terminal, pour extraire les classements par tag pour un
Le settag est identifié sur la base du dernier semestre (ici le 'S3') ; groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous
les étudiants considérés sont donc ceux inscrits dans ce S3 participé au semestre terminal.
à 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). Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S'
""" """
# -------------------------------------------------------------------------------------------------------------------
def __init__( def __init__(
self, self,
nom, nom: str,
formsemestre_terminal: FormSemestre, trajectoire: Trajectoire,
semestres_aggreges: dict[int, FormSemestre],
semestres_taggues: dict[int, SemestreTag], semestres_taggues: dict[int, SemestreTag],
donnees_etudiants: EtudiantsJuryPE, 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""" """Le formsemestre terminal et les semestres aggrégés"""
self.formsemestre_terminal = formsemestre_terminal self.formsemestre_terminal = trajectoire.semestre_final
nt = load_formsemestre_results(formsemestre_terminal) nt = load_formsemestre_results(self.formsemestre_terminal)
self.semestres_aggreges = semestres_aggreges
self.semestres_aggreges = trajectoire.semestres_aggreges
"""Les semestres tags associés aux semestres aggrégés""" """Les semestres tags associés aux semestres aggrégés"""
try: try:
@ -83,6 +92,7 @@ class SetTag(pe_tagtable.TableTag):
"""Les étudiants (état civil + cursus connu)""" """Les étudiants (état civil + cursus connu)"""
self.etuds = nt.etuds 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.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
self.cursus = { self.cursus = {
etudid: donnees_etudiants.cursus[etudid] for etudid in self.etudiants etudid: donnees_etudiants.cursus[etudid] for etudid in self.etudiants
@ -155,7 +165,6 @@ class SetTag(pe_tagtable.TableTag):
def get_etudids(self): def get_etudids(self):
return list(self.etudiants.keys()) return list(self.etudiants.keys())
# -------------------------------------------------------------------------------------------------------------------
def do_taglist(self): def do_taglist(self):
"""Synthétise les tags à partir des semestres (taggués) aggrégés """Synthétise les tags à partir des semestres (taggués) aggrégés