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
13 changed files with 491 additions and 398 deletions
Showing only changes of commit 0f446fe0d3 - Show all commits

View File

@ -47,7 +47,7 @@ from flask import g
import app.scodoc.sco_utils as scu
from app.models import FormSemestre
from app.pe.rcss.rcss_constantes import TYPES_RCS
from app.pe.rcss.pe_rcs import TYPES_RCS
from app.scodoc import sco_formsemestre
from app.scodoc.sco_logos import find_logo
@ -317,3 +317,23 @@ def find_index_and_columns_communs(
colonnes2 = df2.columns
colonnes_communes = list(set(colonnes1) & set(colonnes2))
return indices_communs, colonnes_communes
def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSemestre:
"""Renvoie le dernier semestre en **date de fin** d'un dictionnaire
de semestres (potentiellement non trié) de la forme ``{fid: FormSemestre(fid)}``.
Args:
semestres: Un dictionnaire de semestres
Return:
Le FormSemestre du semestre le plus récent
"""
if semestres:
fid_dernier_semestre = list(semestres.keys())[0]
dernier_semestre: FormSemestre = semestres[fid_dernier_semestre]
for fid in semestres:
if semestres[fid].date_fin > dernier_semestre.date_fin:
dernier_semestre = semestres[fid]
return dernier_semestre
return None

View File

@ -59,10 +59,10 @@ class EtudiantsJuryPE:
self.identites: dict[int, Identite] = {} # ex. ETUDINFO_DICT
"Les identités des étudiants traités pour le jury"
self.trajectoires: dict[int, dict] = {}
self.cursus: dict[int, dict] = {}
"Les cursus (semestres suivis, abandons) des étudiants"
self.trajectoires = {}
self.cursus = {}
"""Les trajectoires/chemins de semestres suivis par les étudiants
pour atteindre un aggrégat donné
(par ex: 3S=S1+S2+S3 à prendre en compte avec d'éventuels redoublements)"""
@ -164,7 +164,7 @@ class EtudiantsJuryPE:
"""
etudids = [
etudid
for etudid, cursus_etud in self.trajectoires.items()
for etudid, cursus_etud in self.cursus.items()
if cursus_etud["diplome"] == self.annee_diplome
and cursus_etud["abandon"] is False
]
@ -181,18 +181,14 @@ class EtudiantsJuryPE:
"""
etudids = [
etudid
for etudid, cursus_etud in self.trajectoires.items()
for etudid, cursus_etud in self.cursus.items()
if cursus_etud["diplome"] != self.annee_diplome
or cursus_etud["abandon"] is True
]
etudiants = {etudid: self.identites[etudid] for etudid in etudids}
return etudiants
def analyse_etat_etudiant(
self,
etudid: int,
cosemestres: dict[int, FormSemestre]
):
def analyse_etat_etudiant(self, etudid: int, cosemestres: dict[int, FormSemestre]):
"""Analyse le cursus d'un étudiant pouvant être :
* l'un de ceux sur lesquels le jury va statuer (année de diplômation du jury considéré)
@ -225,7 +221,7 @@ class EtudiantsJuryPE:
if formsemestre.formation.is_apc()
}
self.trajectoires[etudid] = {
self.cursus[etudid] = {
"etudid": etudid, # les infos sur l'étudiant
"etat_civil": identite.etat_civil, # Ajout à la table jury
"nom": identite.nom,
@ -241,16 +237,16 @@ class EtudiantsJuryPE:
}
# Si l'étudiant est succeptible d'être diplomé
if self.trajectoires[etudid]["diplome"] == self.annee_diplome:
if self.cursus[etudid]["diplome"] == self.annee_diplome:
# Est-il démissionnaire : charge son dernier semestre pour connaitre son état ?
dernier_semes_etudiant = formsemestres[0]
res = load_formsemestre_results(dernier_semes_etudiant)
etud_etat = res.get_etud_etat(etudid)
if etud_etat == scu.DEMISSION:
self.trajectoires[etudid]["abandon"] = True
self.cursus[etudid]["abandon"] = True
else:
# Est-il réorienté ou a-t-il arrêté (volontairement) sa formation ?
self.trajectoires[etudid]["abandon"] = arret_de_formation(
self.cursus[etudid]["abandon"] = arret_de_formation(
identite, cosemestres
)
@ -270,7 +266,7 @@ class EtudiantsJuryPE:
Un dictionnaire ``{fid: FormSemestre(fid)}`` dans lequel les semestres
amènent à une diplômation antérieur à celle de la diplômation visée par le jury jury
"""
semestres_etudiant = self.trajectoires[etudid]["formsemestres"]
semestres_etudiant = self.cursus[etudid]["formsemestres"]
semestres_significatifs = {}
for fid in semestres_etudiant:
semestre = semestres_etudiant[fid]
@ -297,11 +293,9 @@ class EtudiantsJuryPE:
for fid, sem_sig in semestres_significatifs.items()
if sem_sig.semestre_id == i
}
self.trajectoires[etudid][f"S{i}"] = semestres_i
self.cursus[etudid][f"S{i}"] = semestres_i
def get_formsemestres_finals_des_rcs(
self, nom_rcs: str
) -> dict[int, FormSemestre]:
def get_formsemestres_finals_des_rcs(self, nom_rcs: str) -> dict[int, FormSemestre]:
"""Pour un nom de RCS donné, ensemble des formsemestres finals possibles
pour les RCS. Par ex. un RCS '3S' incluant S1+S2+S3 a pour semestre final un S3.
Les formsemestres finals obtenus traduisent :
@ -321,7 +315,7 @@ class EtudiantsJuryPE:
Un dictionnaire ``{fid: FormSemestre(fid)}``
"""
formsemestres_terminaux = {}
for trajectoire_aggr in self.trajectoires.values():
for trajectoire_aggr in self.cursus.values():
trajectoire = trajectoire_aggr[nom_rcs]
if trajectoire:
# Le semestre terminal de l'étudiant de l'aggrégat
@ -338,7 +332,7 @@ class EtudiantsJuryPE:
"""
nbres_semestres = []
for etudid in etudids:
nbres_semestres.append(self.trajectoires[etudid]["nb_semestres"])
nbres_semestres.append(self.cursus[etudid]["nb_semestres"])
if not nbres_semestres:
return 0
return max(nbres_semestres)
@ -359,7 +353,7 @@ class EtudiantsJuryPE:
for etudid in etudids:
etudiant = self.identites[etudid]
cursus = self.trajectoires[etudid]
cursus = self.cursus[etudid]
formsemestres = cursus["formsemestres"]
if cursus["diplome"]:
@ -551,7 +545,7 @@ def arret_de_formation(etud: Identite, cosemestres: dict[int, FormSemestre]) ->
]
affichage = ", ".join([f"S{val}" for val in non_inscrit_a])
pe_affichage.pe_print(
f"{etud.etat_civil} ({etud.etudid}) considéré en abandon car non inscrit dans un (ou des) semestre(s) {affichage} amenant à diplômation"
f"--> ⛔ {etud.etat_civil} ({etud.etudid}), non inscrit dans {affichage} amenant à diplômation"
)
return est_demissionnaire
@ -593,26 +587,6 @@ def arret_de_formation(etud: Identite, cosemestres: dict[int, FormSemestre]) ->
# return False
def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSemestre:
"""Renvoie le dernier semestre en **date de fin** d'un dictionnaire
de semestres (potentiellement non trié) de la forme ``{fid: FormSemestre(fid)}``.
Args:
semestres: Un dictionnaire de semestres
Return:
Le FormSemestre du semestre le plus récent
"""
if semestres:
fid_dernier_semestre = list(semestres.keys())[0]
dernier_semestre: FormSemestre = semestres[fid_dernier_semestre]
for fid in semestres:
if semestres[fid].date_fin > dernier_semestre.date_fin:
dernier_semestre = semestres[fid]
return dernier_semestre
return None
def etapes_du_cursus(
semestres: dict[int, FormSemestre], nbre_etapes_max: int
) -> list[str]:
@ -679,4 +653,3 @@ def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str:
description.append(f"({semestre.formsemestre_id})")
return " ".join(description)

View File

@ -83,8 +83,8 @@ class RCSInterclasseTag(TableTag):
# celles associées à l'aggrégat
self.rcss: dict[int, pe_rcs.RCS] = {}
"""Ensemble des trajectoires associées à l'aggrégat"""
for trajectoire_id in rcss_jury_pe.rcss:
trajectoire = rcss_jury_pe.rcss[trajectoire_id]
for trajectoire_id in rcss_jury_pe.trajectoires:
trajectoire = rcss_jury_pe.trajectoires[trajectoire_id]
if trajectoire_id[0] == nom_rcs:
self.rcss[trajectoire_id] = trajectoire
@ -99,7 +99,7 @@ class RCSInterclasseTag(TableTag):
"""Association entre chaque étudiant et la trajectoire tagguée à prendre en
compte pour l'aggrégat"""
for etudid in self.diplomes_ids:
self.suivi[etudid] = rcss_jury_pe.rcss_suivis[etudid][nom_rcs]
self.suivi[etudid] = rcss_jury_pe.trajectoires_suivies[etudid][nom_rcs]
self.tags_sorted = self.do_taglist()
"""Liste des tags (triés par ordre alphabétique)"""

View File

@ -50,17 +50,15 @@ from zipfile import ZipFile
import numpy as np
import pandas as pd
import app.pe.rcss.rcss_constantes as rcss_constants
from app.pe.rcss import pe_rcs
from app.pe import pe_sxtag
from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE
import app.pe.pe_affichage as pe_affichage
import app.pe.pe_etudiant as pe_etudiant
import app.pe.rcss.pe_rcs as pe_rcs
from app.pe.pe_rcstag import RCSTag
from app.pe.pe_ressemtag import ResSemBUTTag
from app.pe.pe_interclasstag import RCSInterclasseTag
import app.pe.pe_rcss_jury as pe_rcss_jury
import app.pe.rcss.rcss_constantes as rcss_constantes
class JuryPE(object):
@ -107,11 +105,11 @@ class JuryPE(object):
try:
self._gen_xls_diplomes(zipfile)
self._gen_xls_ressembuttags(zipfile)
self._gen_rcss()
self._gen_rcsf()
self._gen_trajectoires()
self._gen_semXs()
self._gen_xls_sxtags(zipfile)
self._gen_rcrcfs()
self._gen_xls_rcrcss_tags(zipfile)
# self._gen_rcsemxs()
# self._gen_xls_rcrcss_tags(zipfile)
# self._gen_xls_interclassements_rcss(zipfile)
# self._gen_xls_synthese_jury_par_tag(zipfile)
# self._gen_xls_synthese_par_etudiant(zipfile)
@ -185,35 +183,40 @@ class JuryPE(object):
path="details",
)
def _gen_rcss(self):
"""Génère les RCS (attribut `rcss_jury`), combinaisons de semestres
suivis par les étudiants au sens d'un nom de RCS (par ex: 'S2' ou '3S').
def _gen_trajectoires(self):
"""Génère l'ensemble des trajectoires (RCS), qui traduisent les différents
chemins au sein des (form)semestres pour atteindre la cible d'un
RCS (par ex: 'S2' ou '3S').
"""
pe_affichage.pe_print(
"*** Génère les RCS (différentes combinaisons de semestres) des étudiants"
"*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants"
)
self.rcss_jury.cree_rcss(self.etudiants)
self.rcss_jury.cree_trajectoires(self.etudiants)
def _gen_rcsf(self):
"""Génère les RCF, regroupement de semestres de type Sx pour préparer
le calcul des moyennes par Sx"""
def _gen_semXs(self):
"""Génère les SemXs (trajectoires/combinaisons de semestre de même rang x)
qui traduisent les différents chemins des étudiants pour valider un semestre Sx.
"""
# Génère les regroupements de semestres de type Sx
pe_affichage.pe_print(
"*** Génère les RCSValid (RCS de même Sx donnant lieu à validation du semestre)"
"*** Génère les SemXs (RCS de même Sx donnant lieu à validation du semestre)"
)
self.rcss_jury.cree_rcfs(self.etudiants)
self.rcss_jury.cree_semxs(self.etudiants)
self.rcss_jury._aff_semxs_suivis(self.etudiants)
def _gen_xls_sxtags(self, zipfile: ZipFile):
"""Génère les semestres taggués en s'appuyant sur les RCF de type Sx (pour
identifier les redoublements impactant les semestres taggués).
"""
# Génère les moyennes des RCS de type Sx
pe_affichage.pe_print("*** Calcule les moyennes des SxTag")
pe_affichage.pe_print(
"*** Calcule les moyennes des SxTag (moyennes d'un SemX/RCS de type Sx)"
)
# Les SxTag (moyenne de Sx par UE)
self.sxtags = {}
for rcf_id, rcf in self.rcss_jury.rcfs.items():
for rcf_id, rcf in self.rcss_jury.semXs.items():
# SxTag traduisant le RCF
sxtag_id = rcf_id
@ -242,13 +245,15 @@ class JuryPE(object):
path="details",
)
def _gen_rcrcfs(self):
def _gen_rcsemxs(self):
"""Génère les regroupements cohérents de RCFs qu'ont suivi chaque étudiant"""
pe_affichage.pe_print(
"*** Génère les RCRCF (regroupements de RCF de type Sx) amenant du S1 à un semestre final***"
"*** Génère les RCSemX (regroupements cohérents de données"
" extraites des SemX) amenant du S1 à un semestre final***"
)
self.rcss_jury.cree_rcrcfs(self.etudiants)
self.rcss_jury.cree_rcsemxs(self.etudiants)
self.rcss_jury._aff_rcsemxs_suivis(self.etudiants)
def _gen_xls_rcrcss_tags(self, zipfile: ZipFile):
"""Génère les RCS taggués traduisant les moyennes (orientées compétences)
@ -273,7 +278,7 @@ class JuryPE(object):
pe_affichage.pe_print("*** Calcule les moyennes des RC de RCFS")
self.rcss_tags = {}
for rcs_id, rcrcf in self.rcss_jury.rcrcfs.items():
for rcs_id, rcrcf in self.rcss_jury.rcsemxs.items():
self.rcss_tags[rcs_id] = RCSTag(rcrcf, self.sxtags)
# Intègre le bilan des trajectoires tagguées au zip final
@ -458,13 +463,13 @@ class JuryPE(object):
# Ajout des aggrégats
for aggregat in pe_rcs.TOUS_LES_RCS:
descr = rcss_constantes.TYPES_RCS[aggregat]["descr"]
descr = app.pe.rcss.pe_rcs.TYPES_RCS[aggregat]["descr"]
# Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag
# considéré
trajectoires_tagguees = []
for etudid in etudids:
trajectoire = self.rcss_jury.rcss_suivis[etudid][aggregat]
trajectoire = self.rcss_jury.trajectoires_suivies[etudid][aggregat]
if trajectoire:
tid = trajectoire.sxtag_id
trajectoire_tagguee = self.rcss_tags[tid]
@ -610,7 +615,7 @@ class JuryPE(object):
)
# La trajectoire de l'étudiant sur l'aggrégat
trajectoire = self.rcss_jury.rcss_suivis[etudid][aggregat]
trajectoire = self.rcss_jury.trajectoires_suivies[etudid][aggregat]
if trajectoire:
trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id]
if tag in trajectoire_tagguee.moyennes_tags:
@ -651,9 +656,9 @@ def get_formsemestres_etudiants(etudiants: pe_etudiant.EtudiantsJuryPE) -> dict:
"""
semestres = {}
for etudid in etudiants.etudiants_ids:
for cle in etudiants.trajectoires[etudid]:
for cle in etudiants.cursus[etudid]:
if cle.startswith("S"):
semestres = semestres | etudiants.trajectoires[etudid][cle]
semestres = semestres | etudiants.cursus[etudid][cle]
return semestres

View File

@ -1,9 +1,7 @@
import app.pe.rcss.pe_rcf as pe_rcf
import app.pe.rcss.pe_rcrcf as pe_rcrcf
import app.pe.pe_comp
from app.pe.rcss import pe_rcs, pe_trajectoires, pe_rcsemx
import app.pe.pe_etudiant as pe_etudiant
import app.pe.pe_comp as pe_comp
import app.pe.rcss.rcss_constantes as rcss_constantes
from app.models import FormSemestre
from app.pe import pe_affichage
@ -20,159 +18,198 @@ class RCSsJuryPE:
self.annee_diplome = annee_diplome
"""Année de diplômation"""
self.rcss: dict[tuple(int, str): pe_rcf.RCF] = {}
"""Ensemble des RCS recensés"""
self.trajectoires: dict[tuple(int, str) : pe_trajectoires.Trajectoire] = {}
"""Ensemble des trajectoires recensées (regroupement de (form)semestres BUT)"""
self.rcss_suivis: dict[int:dict] = {}
self.trajectoires_suivies: dict[int:dict] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
son RCS : {etudid: {nom_RCS: RCS}}"""
sa Trajectoire : {etudid: {nom_RCS: Trajectoire}}"""
self.rcfs: dict[tuple(int, str) : pe_rcf.RCF] = {}
"""Ensemble des RCF recensés : {(nom_RCS, fid_terminal): RCF}"""
self.semXs: dict[tuple(int, str) : pe_trajectoires.SemX] = {}
"""Ensemble des SemX recensés (regroupement de (form)semestre BUT de rang x) :
{(nom_RCS, fid_terminal): SemX}"""
self.rcfs_suivis: dict[int:dict] = {}
self.semXs_suivis: dict[int:dict] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque RCS de type Sx,
son SemX : {etudid: {nom_RCS_de_type_Sx: SemX}}"""
self.rcsemxs: dict[tuple(int, str) : pe_rcsemx.RCSemX] = {}
"""Ensemble des RCSemX (regroupement de SemX donnant les résultats aux sems de rang x)
recensés : {(nom_RCS, fid_terminal): RCSemX}"""
self.rcsemxs_suivis: dict[int:str] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
son RCS : {etudid: {nom_RCS: RCF}}"""
son RCSemX : {etudid: {nom_RCS: RCSemX}}"""
self.rcrcfs: dict[tuple(int, str) : pe_rcrcf.RCRCF] = {}
"""Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCRCF}"""
self.rcrcfs_suivis: dict[int:str] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
son RCRCF : {etudid: {nom_RCS: RCSx}}"""
def cree_rcss(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé tous les RCS, au regard du cursus des étudiants
def cree_trajectoires(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé toutes les trajectoires, au regard du cursus des étudiants
analysés + les mémorise dans les données de l'étudiant
Args:
etudiants: Les étudiants à prendre en compte dans le Jury PE
"""
tous_les_aggregats = rcss_constantes.TOUS_LES_SEMESTRES + rcss_constantes.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
for etudid in etudiants.trajectoires:
self.rcss_suivis[etudid] = {
aggregat: None
for aggregat in tous_les_aggregats
tous_les_aggregats = (
pe_rcs.TOUS_LES_SEMESTRES + pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
)
for etudid in etudiants.cursus:
self.trajectoires_suivies[etudid] = {
aggregat: None for aggregat in tous_les_aggregats
}
for nom_rcs in 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 = rcss_constantes.TYPES_RCS[nom_rcs]["aggregat"]
noms_semestre_de_aggregat = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"]
nom_semestre_terminal = noms_semestre_de_aggregat[-1]
for etudid in etudiants.trajectoires:
for etudid in etudiants.cursus:
# Le formsemestre terminal (dernier en date) associé au
# semestre marquant la fin de l'aggrégat
# (par ex: son dernier S3 en date)
trajectoire = etudiants.trajectoires[etudid][nom_semestre_terminal]
trajectoire = etudiants.cursus[etudid][nom_semestre_terminal]
if trajectoire:
formsemestre_final = pe_etudiant.get_dernier_semestre_en_date(trajectoire)
formsemestre_final = app.pe.pe_comp.get_dernier_semestre_en_date(
trajectoire
)
# Ajout ou récupération du RCS associé
rcs_id = (nom_rcs, formsemestre_final.formsemestre_id)
if rcs_id not in self.rcss:
self.rcss[rcs_id] = pe_rcf.RCF(nom_rcs, formsemestre_final)
rcs = self.rcss[rcs_id]
if rcs_id not in self.trajectoires:
self.trajectoires[rcs_id] = pe_trajectoires.Trajectoire(
nom_rcs, formsemestre_final
)
rcs = self.trajectoires[rcs_id]
# La liste des semestres de l'étudiant à prendre en compte
# pour cette trajectoire
semestres_a_aggreger = get_rcs_etudiant(
etudiants.trajectoires[etudid], formsemestre_final, nom_rcs
etudiants.cursus[etudid], formsemestre_final, nom_rcs
)
# Ajout des semestres au RCS
rcs.add_semestres_a_aggreger(semestres_a_aggreger)
rcs.add_semestres(semestres_a_aggreger)
# Mémorise le RCS suivi par l'étudiant
self.rcss_suivis[etudid][nom_rcs] = rcs
self.trajectoires_suivies[etudid][nom_rcs] = rcs
# Affichage pour debug
jeunes = list(enumerate(self.rcss_suivis))
jeunes = list(enumerate(self.trajectoires_suivies))
for no_etud, etudid in jeunes[:20]:
pe_affichage.pe_print(f"--> {etudiants.identites[etudid].nomprenom} (#{etudid}) :")
for nom_rcs, rcs in self.rcss_suivis[etudid].items():
pe_affichage.pe_print(
f"--> {etudiants.identites[etudid].nomprenom} (#{etudid}) :"
)
for nom_rcs, rcs in self.trajectoires_suivies[etudid].items():
if rcs:
pe_affichage.pe_print(f" > RCS {nom_rcs}: {rcs.get_repr()}")
def cree_rcfs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé les RCFs en ne conservant dans les RCS que les regroupements
def cree_semxs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé les les SemXs (trajectoires/combinaisons de semestre de même rang x),
en ne conservant dans les trajectoires que les regroupements
de type Sx"""
self.rcfs = {}
for rcs_id, rcs in self.rcss.items():
if rcs and rcs.nom in rcss_constantes.TOUS_LES_SEMESTRES:
self.rcfs[rcs_id] = rcs
self.semXs = {}
for rcs_id, trajectoire in self.trajectoires.items():
if trajectoire and trajectoire.nom in pe_rcs.TOUS_LES_SEMESTRES:
self.semXs[rcs_id] = pe_trajectoires.SemX(trajectoire)
for etudid in self.rcss_suivis:
for nom_rcs, rcs in self.rcss_suivis[etudid].items():
if rcs and nom_rcs in rcss_constantes.TOUS_LES_SEMESTRES:
if etudid not in self.rcfs_suivis:
self.rcfs_suivis[etudid] = {}
self.rcfs_suivis[etudid][nom_rcs] = rcs
self.semXs_suivis = {}
for etudid in self.trajectoires_suivies:
self.semXs_suivis[etudid] = {
nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES
}
# Affichage pour debug
jeunes = list(enumerate(self.rcfs_suivis))
for nom_rcs, trajectoire in self.trajectoires_suivies[etudid].items():
if trajectoire and nom_rcs in pe_rcs.TOUS_LES_SEMESTRES:
rcs_id = trajectoire.rcs_id
self.semXs_suivis[etudid][nom_rcs] = self.semXs[rcs_id]
def _aff_semxs_suivis(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Affichage des SemX pour debug"""
jeunes = list(enumerate(self.semXs_suivis))
vides = []
for no_etud, etudid in jeunes[:20]:
pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :")
for nom_rcs, rcs in self.rcfs_suivis[etudid].items():
for nom_rcs, rcs in self.semXs_suivis[etudid].items():
if rcs:
pe_affichage.pe_print(f" > RCSValid {nom_rcs}: {rcs.get_repr()}")
pe_affichage.pe_print(f" > SemX {nom_rcs}: {rcs.get_repr()}")
else:
pe_affichage.pe_print(f" > RCSValid {nom_rcs}: <vide>")
vides += [nom_rcs]
vides = sorted(list(set(vides)))
pe_affichage.pe_print(f"-> ⚠ SemX vides : {', '.join(vides)}")
def cree_rcrcfs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé tous les RCRCF, au regard du cursus des étudiants
def cree_rcsemxs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé tous les RCSemXs, au regard du cursus des étudiants
analysés (trajectoires traduisant son parcours dans les
différents semestres) + les mémorise dans les données de l'étudiant
"""
self.rcsemxs_suivis = {nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_RCS}
self.rcsemxs = {}
# Pour tous les étudiants du jury
for etudid in self.rcss_suivis:
self.rcrcfs_suivis[etudid] = {}
for etudid in self.trajectoires_suivies:
self.rcsemxs_suivis[etudid] = {}
for nom_rcs, rcf in self.rcfs_suivis[etudid].items(): # Pour chaque RCS
semestres_a_aggreger = rcf.semestres_aggreges
# Recopie des SemX & des suivis associés
for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES:
trajectoire = self.semXs_suivis[etudid][nom_rcs]
if trajectoire:
self.rcsemxs[trajectoire.rcs_id] = trajectoire
self.rcsemxs_suivis[etudid][nom_rcs] = trajectoire
# Tri des semestres par rang
semestres_tries = pe_comp.tri_semestres_par_rang(semestres_a_aggreger)
# Récupére les RCFs de type Sx traduisant sa trajectoire
rcfs_a_aggreger = {}
for semestres_du_rang in semestres_tries.values():
if semestres_du_rang:
rcf_id = get_rcf_from_semestres_aggreges(
self.rcfs, semestres_du_rang
# Pour chaque aggréggat de type xA ou Sx
for nom_rcs in pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM:
trajectoire = self.trajectoires_suivies[etudid][nom_rcs]
if not trajectoire:
self.rcsemxs_suivis[etudid][nom_rcs] = None
else:
# Identifiant de la trajectoire => donnera ceux du RCSemX
tid = trajectoire.rcs_id
# Ajout du RCSemX
if tid not in self.rcsemxs:
self.rcsemxs[tid] = pe_rcsemx.RCSemX(
trajectoire.nom, trajectoire.formsemestre_final
)
if not rcf_id:
raise ValueError(
"Il manque un RCF pour créer les RCRCFs dans cree_rcrcfs"
# Récupére les SemX (RC de type Sx) associés aux semestres de son cursus
# Par ex: dans S1+S2+S1+S2+S3 => les 2 S1 devient le SemX('S1'), les 2 S2 le SemX('S2'), etc..
# Les Sx pris en compte dans l'aggrégat
noms_sems_aggregat = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"]
semxs_a_aggreger = {}
for Sx in noms_sems_aggregat:
semestres_etudiants = etudiants.cursus[etudid][Sx]
semx_id = get_semx_from_semestres_aggreges(
self.semXs, semestres_etudiants
)
rcfs_a_aggreger[rcf_id] = self.rcfs[rcf_id]
if not semx_id:
raise (
"Il manque un SemX pour créer les RCSemX dans cree_rcsemxs"
)
# Les SemX à ajouter au RCSemX
semxs_a_aggreger[semx_id] = self.semXs[semx_id]
# Ajout du RCRCF
if rcf_id not in self.rcrcfs:
rcf_nom = rcf_id[0]
self.rcrcfs[rcf_id] = pe_rcrcf.RCRCF(rcf_nom, rcf.formsemestre_final)
rcrcf = self.rcrcfs[rcf_id]
# Ajout des SemX à ceux à aggréger dans le RCSemX
rcsemx = self.rcsemxs[tid]
rcsemx.add_semXs(semxs_a_aggreger)
# Ajout des RCFs au RCRCF
rcrcf.add_rcfs_a_aggreger(rcfs_a_aggreger)
# Mémoire la trajectoire RCRCF suivie par l'étudiant
nom_rcs = rcrcf.nom
self.rcrcfs_suivis[etudid][nom_rcs] = rcrcf
# Mémoire du RCSemX aux informations de suivi de l'étudiant
self.rcsemxs_suivis[etudid][nom_rcs] = rcsemx
def _aff_rcsemxs_suivis(self, etudiants):
"""Affiche les RCSemX suivis par les étudiants"""
# Affichage pour debug
jeunes = list(enumerate(self.rcrcfs_suivis))
jeunes = list(enumerate(self.rcsemxs_suivis))
vides = []
for no_etud, etudid in jeunes[:20]:
pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :")
for nom_rcs, rcs in self.rcrcfs_suivis[etudid].items():
for nom_rcs, rcs in self.rcsemxs_suivis[etudid].items():
if rcs:
pe_affichage.pe_print(f" > RCRCF {nom_rcs}: {rcs.get_repr()}")
pe_affichage.pe_print(f" > RCSemX {nom_rcs}: {rcs.get_repr()}")
else:
pe_affichage.pe_print(f" > RCRCF {nom_rcs}: <vide> !!! ")
vides += [f"{nom_rcs}"]
pe_affichage.pe_print(f"-> ⚠ RCSemX vides : {', '.join(list(set(vides)))}")
def get_rcs_etudiant(
@ -204,7 +241,7 @@ def get_rcs_etudiant(
numero_semestres_possibles = [numero_semestre_terminal]
elif nom_rcs.endswith("A"): # les années
numero_semestres_possibles = [
int(sem[-1]) for sem in rcss_constantes.TYPES_RCS[nom_rcs]["aggregat"]
int(sem[-1]) for sem in pe_rcs.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)
@ -221,25 +258,52 @@ def get_rcs_etudiant(
return semestres_aggreges
def get_rcf_from_semestres_aggreges(
rcfs: dict[(str, int):pe_rcf.RCF], semestres_a_aggreges: list[FormSemestre]
def get_semx_from_semestres_aggreges(
semXs: dict[(str, int) : pe_trajectoires.SemX],
semestres_a_aggreger: dict[(str, int):FormSemestre],
) -> (str, int):
"""Partant d'un dictionnaire de RCFs (de la forme
``{ (nom_rcs, fid): RCF }, et connaissant une liste
de (form)semestres à aggréger, renvoie l'identifiant
(nom_rcs, fid) du RCFs qui lui correspond (c'est à dire celui dont
les semestres_aggregés par le RCF sont les même que les
semestres_a_aggreger.
"""Partant d'un dictionnaire de SemX (de la forme
``{ (nom_rcs, fid): SemX }, et connaissant une liste
de (form)semestres suivis, renvoie l'identifiant
(nom_rcs, fid) du SemX qui lui correspond.
Le SemX qui correspond est tel que :
* le semestre final du SemX correspond au dernier semestre en date des
semestres_a_aggreger
* le rang du SemX est le même que celui des semestres_aggreges
* les semestres_a_aggreger (plus large, car contenant plusieurs
parcours), matchent avec les semestres aggrégés
par le SemX
Returns:
rcf_id: L'identifiant du RCF trouvé
"""
fids_semestres_a_aggreger = set(
[frms.formsemestre_id for frms in semestres_a_aggreges]
)
for rcf_id, rcf in rcfs.items():
fids_rcf = set(rcf.semestres_aggreges)
if fids_rcf == fids_semestres_a_aggreger:
return rcf_id
return None
assert semestres_a_aggreger, "Pas de semestres à aggréger"
rangs_a_aggreger = [sem.semestre_id for fid, sem in semestres_a_aggreger.items()]
assert (
len(set(rangs_a_aggreger)) == 1
), "Tous les sem à aggréger doivent être de même rang"
# Le dernier semestre des semestres à regrouper
dernier_sem_a_aggreger = pe_comp.get_dernier_semestre_en_date(semestres_a_aggreger)
semxs_ids = [] # Au cas où il y ait plusieurs solutions
for semx_id, semx in semXs.items():
# Même semestre final ?
if semx.get_formsemestre_id_final() == dernier_sem_a_aggreger.formsemestre_id:
# Les fids
fids_a_aggreger = set(semestres_a_aggreger.keys())
# Ceux du semx
fids_semx = set(semx.semestres_aggreges.keys())
if fids_a_aggreger.issubset(
fids_semx
): # tous les semestres du semx correspond à des sems de la trajectoire
semxs_ids += [semx_id]
if len(semxs_ids) == 0:
return None # rien trouvé
elif len(semxs_ids) == 1:
return semxs_ids[0]
else:
raise "Plusieurs solutions :)"

View File

@ -42,7 +42,7 @@ from app.pe import pe_affichage
import pandas as pd
import numpy as np
import app.pe.rcss.pe_rcs as pe_rcs
import app.pe.rcss.pe_rcrcf as pe_rcrcf
import app.pe.rcss.pe_rcsemx as pe_rcrcf
import app.pe.pe_sxtag as pe_sxtag
import app.pe.pe_comp as pe_comp
from app.pe.pe_tabletags import TableTag
@ -66,7 +66,7 @@ class RCSTag(TableTag):
self.rcs_id: tuple(str, int) = rcrcf.rcs_id
"""Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)"""
self.rcrcf: pe_rcrcf.RCRCF = rcrcf
self.rcrcf: pe_rcrcf.RCSemX = rcrcf
"""RCRCF associé au RCS taggué"""
self.nom = self.get_repr()
@ -82,12 +82,12 @@ class RCSTag(TableTag):
pe_affichage.pe_print(f"-> {self.get_repr(verbose=True)}")
# Les données aggrégés (RCRCF + SxTags
self.rcfs_aggreges = rcrcf.rcfs_aggreges
self.rcsemxs_aggreges = rcrcf.rcsemxs_aggreges
"""Les RCFs aggrégés"""
self.sxstags = {}
"""Les SxTag associés aux RCF aggrégés"""
try:
for rcf_id in self.rcfs_aggreges:
for rcf_id in self.rcsemxs_aggreges:
self.sxstags[rcf_id] = sxstags[rcf_id]
except:
raise ValueError("Semestres SxTag manquants")

View File

@ -43,14 +43,14 @@ import numpy as np
from app.pe.pe_tabletags import TableTag
from app.pe.pe_moytag import MoyennesTag
import app.pe.rcss.pe_rcf as pe_rcf
import app.pe.rcss.pe_trajectoires as pe_trajectoires
class SxTag(TableTag):
def __init__(
self,
sxtag_id: (str, int),
rcf: pe_rcf.RCF,
semx: pe_trajectoires.SemX,
ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag],
):
"""Calcule les moyennes/classements par tag d'un semestre de type 'Sx'
@ -86,13 +86,22 @@ class SxTag(TableTag):
self.sxtag_id: (str, int) = sxtag_id
"""Identifiant du SxTag de la forme (nom_Sx, fid_semestre_final)"""
assert (
len(self.sxtag_id) == 2
and isinstance(self.sxtag_id[0], str)
and isinstance(self.sxtag_id[1], int)
), "Format de l'identifiant du SxTag non respecté"
self.rcf = rcf
"""Le RCF sur lequel il s'appuie"""
assert rcf.rcs_id == sxtag_id, "Problème de correspondance SxTag/RCF"
self.nom_rcs = sxtag_id[0]
# Les resultats des semestres taggués à prendre en compte dans le RCF
self.ressembuttags = {fid: ressembuttags[fid] for fid in rcf.semestres_aggreges}
self.semx = semx
"""Le SemX sur lequel il s'appuie"""
assert semx.rcs_id == sxtag_id, "Problème de correspondance SxTag/SemX"
# Les resultats des semestres taggués à prendre en compte dans le SemX
self.ressembuttags = {
fid: ressembuttags[fid] for fid in semx.semestres_aggreges
}
"""Les ResSemBUTTags à regrouper dans le SxTag"""
# Les données du semestre final
@ -108,7 +117,7 @@ class SxTag(TableTag):
"""Les etudids triés"""
# Affichage
pe_affichage.pe_print(f"--> {self.get_repr()}")
pe_affichage.pe_print(f"--> {self.get_repr(verbose=True)}")
# Les tags
self.tags_sorted = self.ressembuttag_final.tags_sorted
@ -227,10 +236,10 @@ class SxTag(TableTag):
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
est basée)"""
if verbose:
return f"{self.sxtag_id[0]}Tag basé sur {self.rcf.get_repr()}"
return f"SXTag basé sur {self.semx.get_repr()}"
else:
# affichage = [str(fid) for fid in self.ressembuttags]
return f"{self.sxtag_id[0]}Tag (#{self.fid_final})"
return f"SXTag {self.nom_rcs}#{self.fid_final}"
def _aff_capitalisations(self):
"""Affichage des capitalisations du sxtag pour debug"""

View File

@ -1,57 +0,0 @@
from app.models import FormSemestre
import app.pe.rcss.pe_rcs as pe_rcs
class RCF(pe_rcs.RCS):
"""Modélise un ensemble de (form)semestres d'étudiants
associé à un type de regroupement cohérent de semestres
donné (par ex: 'S2', '3S', '2A').
Si le RCF est un semestre de type Si, stocke les
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 RCF de type iS ou iA (par ex, 3A=S1+S2+S3), identifie
les semestres que les étudiants ont suivis pour les amener jusqu'au semestre
terminal du RCS (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 formsemestre final du RCS
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
pe_rcs.RCS.__init__(self, nom_rcs, semestre_final)
self.semestres_aggreges: dict[int:FormSemestre] = {}
"""Formsemestres regroupés dans le RCS"""
def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]):
"""Ajout de semestres aux semestres à regrouper
Args:
semestres: Dictionnaire ``{fid: Formsemestre)``
"""
self.semestres_aggreges = self.semestres_aggreges | semestres
def get_repr(self, verbose=True) -> str:
"""Représentation textuelle d'un RCS
basé sur ses semestres aggrégés"""
title = f"""{self.__class__.__name__} {pe_rcs.RCS.__str__(self)}"""
if verbose:
noms = []
for fid in self.semestres_aggreges:
semestre = self.semestres_aggreges[fid]
noms.append(f"S{semestre.semestre_id}(#{fid})")
noms = sorted(noms)
if noms:
title += " <" + "+".join(noms) + ">"
else:
title += " <vide>"
return title

View File

@ -1,69 +0,0 @@
##############################################################################
# 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
import app.pe.rcss.pe_rcf
import app.pe.rcss.rcss_constantes
from app.models import FormSemestre
from app.pe import pe_sxtag, pe_affichage
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
import app.pe.rcss.pe_rcs as pe_rcs
import app.pe.rcss.pe_rcf as pe_rcf
class RCRCF:
"""Modélise les RCF d'étudiants suivis par un étudiant dans
le cadre d'un RCS donné (par ex: 3S=S1+S2+S3).
Pour rappel : un RCF (par ex. S1) combine les semestres 1 qu'a suivi
l'étudiant pour valider son S1 (1 si étudiant standard, 2 si redoublant).
Le RCRCF 3S est donc le regroupement du RCF S1 + RCF S2 + RCF S3.
Il est identifié par le formsemestre de S3 marquant la fin du regroupement.
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):
pe_rcs.RCS.__init__(self, nom_rcs, semestre_final)
self.rcfs_aggreges: dict[(str, int) : pe_sxtag.SxTag] = {}
"""Les RCFs à aggréger"""
def add_rcfs_a_aggreger(self, rcfs: dict[(str, int): app.pe.rcss.pe_rcf.RCF]):
"""Ajout des RCFs aux RCFS à regrouper
Args:
rcfs: Dictionnaire ``{(str,fid): RCF}`` à ajouter
"""
self.rcfs_aggreges = self.rcfs_aggreges | rcfs
def get_repr(self, verbose=True) -> str:
"""Représentation textuelle d'un RCSF
basé sur ses RCF aggrégés"""
title = f"""{self.__class__.__name__}{pe_rcs.RCS.__str__(self)}"""
if verbose:
noms = []
for rcf_id, rcf in self.rcfs_aggreges.items():
noms.append(rcf.get_repr(verbose=False))
if noms:
title += " <<" + "+".join(noms) + ">>"
else:
title += " <<vide>>"
return title

View File

@ -10,32 +10,100 @@ Created on 01-2024
"""
from app.models import FormSemestre
import app.pe.rcss.rcss_constantes as rcss_constantes
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")]
def get_descr_rcs(nom_rcs: str) -> str:
"""Renvoie la description pour les tableurs de synthèse
Excel d'un nom de RCS"""
return rcss_constantes.TYPES_RCS[nom_rcs]["descr"]
return TYPES_RCS[nom_rcs]["descr"]
class RCS:
"""Modélise un regroupement cohérent de semestres (formsemestre ou de Sx)"""
"""Modélise un regroupement cohérent de semestres,
tous se terminant par un (form)semestre final.
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
self.nom: str = nom_rcs
"""Nom du RCS"""
assert self.nom in TOUS_LES_RCS, "Le nom d'un RCS doit être un aggrégat"
self.formsemestre_final: FormSemestre = semestre_final
"""FormSemestre terminal du RCS"""
"""(Form)Semestre final du RCS"""
self.rang_final = self.formsemestre_final.semestre_id
"""Le rang du formsemestre final"""
"""Rang du formsemestre final"""
self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id)
"""Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)"""
self.fid_final: int = self.formsemestre_final.formsemestre_id
"""Identifiant du (Form)Semestre final"""
def get_formsemestre_id_final(self) -> int:
"""Renvoie l'identifiant du formsemestre final du RCS
@ -58,5 +126,3 @@ class RCS:
self.nom == other.nom
and self.formsemestre_final == other.formsemestre_final
)

59
app/pe/rcss/pe_rcsemx.py Normal file
View File

@ -0,0 +1,59 @@
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
"""
Created on 01-2024
@author: barasc
"""
from app.models import FormSemestre
from app.pe import pe_sxtag, pe_affichage
from app.pe.rcss import pe_rcs, pe_trajectoires
class RCSemX(pe_rcs.RCS):
"""Modélise un regroupement cohérent de SemX (en même regroupant
des semestres Sx combinés pour former les résultats des étudiants
au semestre de rang x) dans le but de synthétiser les résultats
du S1 jusqu'au semestre final ciblé par le RCSemX (dépendant de l'aggrégat
visé).
Par ex: Si l'aggrégat du RCSemX est '3S' (=S1+S2+S3),
regroupement le SemX du S1 + le SemX du S2 + le SemX du S3 (chacun
incluant des infos sur les redoublements).
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):
pe_rcs.RCS.__init__(self, nom_rcs, semestre_final)
self.semXs_aggreges: dict[(str, int) : pe_sxtag.SxTag] = {}
"""Les semX à aggréger"""
def add_semXs(self, semXs: dict[(str, int) : pe_trajectoires.SemX]):
"""Ajoute des semXs aux semXs à regrouper dans le RCSemX
Args:
semXs: Dictionnaire ``{(str,fid): RCF}`` à ajouter
"""
self.semXs_aggreges = self.semXs_aggreges | semXs
def get_repr(self, verbose=True) -> str:
"""Représentation textuelle d'un RCSF
basé sur ses RCF aggrégés"""
title = f"""{self.__class__.__name__} {pe_rcs.RCS.__str__(self)}"""
if verbose:
noms = []
for semx_id, semx in self.semXs_aggreges.items():
noms.append(semx.get_repr(verbose=False))
if noms:
title += " <<" + "+".join(noms) + ">>"
else:
title += " <<vide>>"
return title

View File

@ -0,0 +1,87 @@
from app.models import FormSemestre
import app.pe.rcss.pe_rcs as pe_rcs
class Trajectoire(pe_rcs.RCS):
"""Regroupement Cohérent de Semestres ciblant un type d'aggrégat (par ex.
'S2', '3S', '1A') et un semestre final, et dont les données regroupées
sont des **FormSemestres** suivis par les étudiants.
Une *Trajectoire* traduit la succession de semestres
qu'ont pu suivre des étudiants pour aller d'un semestre S1 jusqu'au semestre final
de l'aggrégat.
Une *Trajectoire* peut être :
* un RCS de semestre de type "Sx" (cf. classe "SemX"), qui stocke les
formsemestres de rang x qu'ont suivi l'étudiant pour valider le Sx
(en général 1 formsemestre pour les non-redoublants et 2 pour les redoublants)
* un RCS de type iS ou iA (par ex, 3A=S1+S2+S3), qui identifie
les formsemestres que des étudiants ont suivis pour les amener jusqu'au semestre
terminal du RCS. Par ex: si le RCS est un 3S:
* 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 formsemestre final du RCS
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
pe_rcs.RCS.__init__(self, nom_rcs, semestre_final)
self.semestres_aggreges: dict[int:FormSemestre] = {}
"""Formsemestres regroupés dans le RCS"""
def add_semestres(self, semestres: dict[int:FormSemestre]):
"""Ajout de semestres aux semestres à regrouper
Args:
semestres: Dictionnaire ``{fid: Formsemestre)``
"""
for sem in semestres.values():
assert isinstance(
sem, FormSemestre
), "Les données aggrégées d'une Trajectoire doivent être des FormSemestres"
self.semestres_aggreges = self.semestres_aggreges | semestres
def get_repr(self, verbose=True) -> str:
"""Représentation textuelle d'un RCS
basé sur ses semestres aggrégés"""
title = f"""{self.__class__.__name__} {pe_rcs.RCS.__str__(self)}"""
if verbose:
noms = []
for fid in self.semestres_aggreges:
semestre = self.semestres_aggreges[fid]
noms.append(f"S{semestre.semestre_id}#{fid}")
noms = sorted(noms)
if noms:
title += " <" + "+".join(noms) + ">"
else:
title += " <vide>"
return title
class SemX(Trajectoire):
"""Trajectoire (regroupement cohérent de (form)semestres
dans laquelle tous les semestres regroupés sont de même rang `x`.
Les SemX stocke les
formsemestres de rang x qu'ont suivi l'étudiant pour valider le Sx
(en général 1 formsemestre pour les non-redoublants et 2 pour les redoublants).
Ils servent à calculer les SemXTag (moyennes par tag des RCS de type `Sx`).
"""
def __init__(self, trajectoire: Trajectoire):
Trajectoire.__init__(self, trajectoire.nom, trajectoire.formsemestre_final)
semestres_aggreges = trajectoire.semestres_aggreges
for sem in semestres_aggreges.values():
assert (
sem.semestre_id == trajectoire.rang_final
), "Tous les semestres aggrégés d'un SemX doivent être de même rang"
self.semestres_aggreges = trajectoire.semestres_aggreges

View File

@ -1,64 +0,0 @@
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")]