forked from ScoDoc/ScoDoc
282 lines
10 KiB
Python
282 lines
10 KiB
Python
##############################################################################
|
|
# Module "Avis de poursuite d'étude"
|
|
# conçu et développé par Cléo Baras (IUT de Grenoble)
|
|
##############################################################################
|
|
|
|
"""
|
|
Created on 01-2024
|
|
|
|
@author: barasc
|
|
"""
|
|
|
|
import app.pe.pe_comp as pe_comp
|
|
|
|
from app.models import FormSemestre
|
|
from app.pe import pe_sxtag
|
|
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
|
|
|
|
TYPES_RCS = {
|
|
"S1": {
|
|
"aggregat": ["S1"],
|
|
"descr": "Semestre 1 (S1)",
|
|
},
|
|
"S2": {
|
|
"aggregat": ["S2"],
|
|
"descr": "Semestre 2 (S2)",
|
|
},
|
|
"1A": {
|
|
"aggregat": ["S1", "S2"],
|
|
"descr": "BUT1 (S1+S2)",
|
|
},
|
|
"S3": {
|
|
"aggregat": ["S3"],
|
|
"descr": "Semestre 3 (S3)",
|
|
},
|
|
"S4": {
|
|
"aggregat": ["S4"],
|
|
"descr": "Semestre 4 (S4)",
|
|
},
|
|
"2A": {
|
|
"aggregat": ["S3", "S4"],
|
|
"descr": "BUT2 (S3+S4)",
|
|
},
|
|
"3S": {
|
|
"aggregat": ["S1", "S2", "S3"],
|
|
"descr": "Moyenne du semestre 1 au semestre 3 (S1+S2+S3)",
|
|
},
|
|
"4S": {
|
|
"aggregat": ["S1", "S2", "S3", "S4"],
|
|
"descr": "Moyenne du semestre 1 au semestre 4 (S1+S2+S3+S4)",
|
|
},
|
|
"S5": {
|
|
"aggregat": ["S5"],
|
|
"descr": "Semestre 5 (S5)",
|
|
},
|
|
"S6": {
|
|
"aggregat": ["S6"],
|
|
"descr": "Semestre 6 (S6)",
|
|
},
|
|
"3A": {
|
|
"aggregat": ["S5", "S6"],
|
|
"descr": "3ème année (S5+S6)",
|
|
},
|
|
"5S": {
|
|
"aggregat": ["S1", "S2", "S3", "S4", "S5"],
|
|
"descr": "Moyenne du semestre 1 au semestre 5 (S1+S2+S3+S4+S5)",
|
|
},
|
|
"6S": {
|
|
"aggregat": ["S1", "S2", "S3", "S4", "S5", "S6"],
|
|
"descr": "Moyenne globale (S1+S2+S3+S4+S5+S6)",
|
|
},
|
|
}
|
|
"""Dictionnaire détaillant les différents regroupements cohérents
|
|
de semestres (RCS), en leur attribuant un nom et en détaillant
|
|
le nom des semestres qu'ils regroupent et l'affichage qui en sera fait
|
|
dans les tableurs de synthèse.
|
|
"""
|
|
|
|
TOUS_LES_RCS_AVEC_PLUSIEURS_SEM = [cle for cle in TYPES_RCS if not cle.startswith("S")]
|
|
TOUS_LES_RCS = list(TYPES_RCS.keys())
|
|
TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")]
|
|
|
|
|
|
class RCS:
|
|
"""Modélise un ensemble de semestres finals d'étudiants
|
|
associé à un type de regroupement cohérent de semestres
|
|
donné (par ex: 'S2', '3S', '2A').
|
|
|
|
Si le RCS est un semestre de type Si, stocke le
|
|
formsemestres de numéro i qu'ont suivi 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 le RCS 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, ...
|
|
|
|
Args:
|
|
nom_rcs: Un nom du RCS (par ex: '5S')
|
|
semestre_final: Le semestre final du RCS
|
|
"""
|
|
|
|
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
|
|
self.nom: str = nom_rcs
|
|
"""Nom du RCS"""
|
|
|
|
self.formsemestre_final: FormSemestre = semestre_final
|
|
"""FormSemestre terminal du RCS"""
|
|
|
|
self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id)
|
|
"""Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)"""
|
|
|
|
# self.semestres_aggreges: dict[int:FormSemestre] = {}
|
|
# """Semestres regroupés dans le RCS"""
|
|
|
|
self.sxtags_aggreges: dict[(str, int): pe_sxtag.SxTag] = {}
|
|
"""Les SxTag aggrégés"""
|
|
|
|
def get_formsemestre_id_final(self):
|
|
"""Renvoie l'identifiant du formsemestre final du RCS"""
|
|
return self.formsemestre_final.formsemestre_id
|
|
|
|
def add_sxtags_a_aggreger(self, sxtags: dict[(str,int): pe_sxtag.SxTag]):
|
|
"""Ajout des SxTag aux semestres à regrouper
|
|
|
|
Args:
|
|
sxtags: Dictionnaire ``{(str,fid): SxTag}`` à ajouter
|
|
"""
|
|
self.sxtags_aggreges = self.sxtags_aggreges | sxtags
|
|
|
|
def get_repr(self, verbose=True) -> str:
|
|
"""Représentation textuelle d'un RCS
|
|
basé sur ses sxtags aggrégés"""
|
|
|
|
noms = []
|
|
for sxtag_id, sxtag in self.sxtags_aggreges.items():
|
|
noms.append(f"S{sxtag.semestre_id}")
|
|
noms = sorted(noms)
|
|
title = f"""{self.nom} ({
|
|
self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"""
|
|
if verbose and noms:
|
|
title += " - " + "+".join(noms)
|
|
return title
|
|
|
|
|
|
class RCSsJuryPE:
|
|
"""Classe centralisant tous les regroupements cohérents de
|
|
semestres (RCS) des étudiants à prendre en compte dans un jury PE
|
|
|
|
Args:
|
|
annee_diplome: L'année de diplomation
|
|
"""
|
|
|
|
def __init__(self, annee_diplome: int):
|
|
self.annee_diplome = annee_diplome
|
|
"""Année de diplômation"""
|
|
|
|
self.rcss: dict[tuple:RCS] = {}
|
|
"""Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCS}"""
|
|
|
|
self.suivi: dict[int:str] = {}
|
|
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
|
|
son RCS : {etudid: {nom_RCS: RCS}}"""
|
|
|
|
def cree_rcss(self, etudiants: EtudiantsJuryPE, sxtags: dict[(str, int), pe_sxtag.SxTag]):
|
|
"""Créé tous les RCS, au regard du cursus des étudiants
|
|
analysés et des SxTag calculés.
|
|
|
|
Les mémorise dans les données de chaque étudiant.
|
|
|
|
Args:
|
|
etudiants: Les étudiants à prendre en compte dans le Jury PE
|
|
pe_sxtag: Les Sx taggués
|
|
"""
|
|
|
|
for nom_rcs in pe_comp.TOUS_LES_SEMESTRES + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM:
|
|
# 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 = TYPES_RCS[nom_rcs]["aggregat"] # ["S1", "S2", "S3"]
|
|
nom_semestre_terminal = noms_semestre_de_aggregat[-1] # "S3"
|
|
|
|
for etudid in etudiants.trajectoires:
|
|
if etudid not in self.suivi:
|
|
self.suivi[etudid] = {
|
|
aggregat: None
|
|
for aggregat in pe_comp.TOUS_LES_SEMESTRES
|
|
+ TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
|
|
}
|
|
|
|
# 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.trajectoires[etudid][nom_semestre_terminal]
|
|
if semestres:
|
|
formsemestre_final = get_dernier_semestre_en_date(semestres)
|
|
|
|
# Ajout ou récupération de la trajectoire
|
|
rcs_id = (nom_rcs, formsemestre_final.formsemestre_id)
|
|
if rcs_id not in self.rcss:
|
|
rcs = RCS(nom_rcs, formsemestre_final)
|
|
self.rcss[rcs_id] = rcs
|
|
else:
|
|
rcs = self.rcss[rcs_id]
|
|
|
|
# La liste des semestres de l'étudiant à prendre en compte
|
|
# pour cette trajectoire
|
|
semestres_a_aggreger = get_trajectoire_etudiant(
|
|
etudiants.trajectoires[etudid], formsemestre_final, nom_rcs
|
|
)
|
|
|
|
# Extrait les sxtags correspondants aux semestres à aggréger
|
|
# (par ex. des 2 semestres S1(18)+S1(26) récupère le sxtag S1(26)
|
|
sxtags_a_aggreger = {}
|
|
semestres_tries = pe_comp.tri_semestres_par_rang(semestres_a_aggreger)
|
|
for rang in semestres_tries:
|
|
sems = semestres_tries[rang] # les 1 ou 2 semestres de même rang suivi
|
|
sxtag_id = pe_sxtag.get_sxtag_from_semestres(sems, sxtags)
|
|
if not sxtag_id:
|
|
raise ValueError(f"Il manque un sxtag pour {sems}")
|
|
sxtags_a_aggreger[sxtag_id] = sxtags[sxtag_id]
|
|
|
|
# Ajout des semestres à la trajectoire
|
|
rcs.add_sxtags_a_aggreger(sxtags_a_aggreger)
|
|
|
|
# Mémoire la trajectoire suivie par l'étudiant
|
|
self.suivi[etudid][nom_rcs] = rcs
|
|
|
|
|
|
def get_trajectoire_etudiant(
|
|
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
|
|
) -> dict[int, FormSemestre]:
|
|
"""Ensemble des semestres parcourus (trajectoire)
|
|
par un étudiant dans le cadre
|
|
d'un RCS de type Sx, iA ou iS et ayant pour semestre terminal `formsemestre_final`.
|
|
|
|
Par ex: pour un RCS "3S", dont le formsemestre_terminal est un S3, regroupe
|
|
le ou les S1 qu'il a suivi (1 ou 2 si redoublement) + le ou les S2 + le ou les S3.
|
|
|
|
Les semestres parcourus sont antérieurs (en terme de date de fin)
|
|
au formsemestre_terminal.
|
|
|
|
Args:
|
|
cursus: Dictionnaire {fid: Formsemestre} donnant l'ensemble des semestres
|
|
dans lesquels l'étudiant a été inscrit
|
|
formsemestre_final: le semestre final visé
|
|
nom_rcs: Nom du RCS visé
|
|
"""
|
|
numero_semestre_terminal = formsemestre_final.semestre_id
|
|
# semestres_significatifs = self.get_semestres_significatifs(etudid)
|
|
semestres_significatifs = {}
|
|
for i in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT + 1):
|
|
semestres_significatifs = semestres_significatifs | semestres[f"S{i}"]
|
|
|
|
if nom_rcs.startswith("S"): # les semestres
|
|
numero_semestres_possibles = [numero_semestre_terminal]
|
|
elif nom_rcs.endswith("A"): # les années
|
|
numero_semestres_possibles = [
|
|
int(sem[-1]) for sem in TYPES_RCS[nom_rcs]["aggregat"]
|
|
]
|
|
assert numero_semestre_terminal in numero_semestres_possibles
|
|
else: # les xS = tous les semestres jusqu'à Sx (eg S1, S2, S3 pour un S3 terminal)
|
|
numero_semestres_possibles = list(range(1, numero_semestre_terminal + 1))
|
|
|
|
semestres_aggreges = {}
|
|
for fid, semestre in semestres_significatifs.items():
|
|
# Semestres parmi ceux de n° possibles & qui lui sont antérieurs
|
|
if (
|
|
semestre.semestre_id in numero_semestres_possibles
|
|
and semestre.date_fin <= formsemestre_final.date_fin
|
|
):
|
|
semestres_aggreges[fid] = semestre
|
|
return semestres_aggreges
|
|
|
|
|
|
def get_descr_rcs(nom_rcs: str) -> str:
|
|
"""Renvoie la description pour les tableurs de synthèse
|
|
Excel d'un nom de RCS"""
|
|
return TYPES_RCS[nom_rcs]["descr"]
|