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 import app.scodoc.sco_utils as scu
from app.models import FormSemestre 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 import sco_formsemestre
from app.scodoc.sco_logos import find_logo from app.scodoc.sco_logos import find_logo
@ -317,3 +317,23 @@ def find_index_and_columns_communs(
colonnes2 = df2.columns colonnes2 = df2.columns
colonnes_communes = list(set(colonnes1) & set(colonnes2)) colonnes_communes = list(set(colonnes1) & set(colonnes2))
return indices_communs, colonnes_communes 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 self.identites: dict[int, Identite] = {} # ex. ETUDINFO_DICT
"Les identités des étudiants traités pour le jury" "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" "Les cursus (semestres suivis, abandons) des étudiants"
self.trajectoires = {} self.cursus = {}
"""Les trajectoires/chemins de semestres suivis par les étudiants """Les trajectoires/chemins de semestres suivis par les étudiants
pour atteindre un aggrégat donné pour atteindre un aggrégat donné
(par ex: 3S=S1+S2+S3 à prendre en compte avec d'éventuels redoublements)""" (par ex: 3S=S1+S2+S3 à prendre en compte avec d'éventuels redoublements)"""
@ -164,7 +164,7 @@ class EtudiantsJuryPE:
""" """
etudids = [ etudids = [
etudid etudid
for etudid, cursus_etud in self.trajectoires.items() for etudid, cursus_etud in self.cursus.items()
if cursus_etud["diplome"] == self.annee_diplome if cursus_etud["diplome"] == self.annee_diplome
and cursus_etud["abandon"] is False and cursus_etud["abandon"] is False
] ]
@ -181,18 +181,14 @@ class EtudiantsJuryPE:
""" """
etudids = [ etudids = [
etudid etudid
for etudid, cursus_etud in self.trajectoires.items() for etudid, cursus_etud in self.cursus.items()
if cursus_etud["diplome"] != self.annee_diplome if cursus_etud["diplome"] != self.annee_diplome
or cursus_etud["abandon"] is True or cursus_etud["abandon"] is True
] ]
etudiants = {etudid: self.identites[etudid] for etudid in etudids} etudiants = {etudid: self.identites[etudid] for etudid in etudids}
return etudiants return etudiants
def analyse_etat_etudiant( def analyse_etat_etudiant(self, etudid: int, cosemestres: dict[int, FormSemestre]):
self,
etudid: int,
cosemestres: dict[int, FormSemestre]
):
"""Analyse le cursus d'un étudiant pouvant être : """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é) * 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() if formsemestre.formation.is_apc()
} }
self.trajectoires[etudid] = { self.cursus[etudid] = {
"etudid": etudid, # les infos sur l'étudiant "etudid": etudid, # les infos sur l'étudiant
"etat_civil": identite.etat_civil, # Ajout à la table jury "etat_civil": identite.etat_civil, # Ajout à la table jury
"nom": identite.nom, "nom": identite.nom,
@ -241,16 +237,16 @@ class EtudiantsJuryPE:
} }
# Si l'étudiant est succeptible d'être diplomé # 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 ? # Est-il démissionnaire : charge son dernier semestre pour connaitre son état ?
dernier_semes_etudiant = formsemestres[0] dernier_semes_etudiant = formsemestres[0]
res = load_formsemestre_results(dernier_semes_etudiant) res = load_formsemestre_results(dernier_semes_etudiant)
etud_etat = res.get_etud_etat(etudid) etud_etat = res.get_etud_etat(etudid)
if etud_etat == scu.DEMISSION: if etud_etat == scu.DEMISSION:
self.trajectoires[etudid]["abandon"] = True self.cursus[etudid]["abandon"] = True
else: else:
# Est-il réorienté ou a-t-il arrêté (volontairement) sa formation ? # 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 identite, cosemestres
) )
@ -270,7 +266,7 @@ class EtudiantsJuryPE:
Un dictionnaire ``{fid: FormSemestre(fid)}`` dans lequel les semestres 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 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 = {} semestres_significatifs = {}
for fid in semestres_etudiant: for fid in semestres_etudiant:
semestre = semestres_etudiant[fid] semestre = semestres_etudiant[fid]
@ -297,11 +293,9 @@ class EtudiantsJuryPE:
for fid, sem_sig in semestres_significatifs.items() for fid, sem_sig in semestres_significatifs.items()
if sem_sig.semestre_id == i 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( def get_formsemestres_finals_des_rcs(self, nom_rcs: str) -> dict[int, FormSemestre]:
self, nom_rcs: str
) -> dict[int, FormSemestre]:
"""Pour un nom de RCS donné, ensemble des formsemestres finals possibles """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. pour les RCS. Par ex. un RCS '3S' incluant S1+S2+S3 a pour semestre final un S3.
Les formsemestres finals obtenus traduisent : Les formsemestres finals obtenus traduisent :
@ -321,7 +315,7 @@ class EtudiantsJuryPE:
Un dictionnaire ``{fid: FormSemestre(fid)}`` Un dictionnaire ``{fid: FormSemestre(fid)}``
""" """
formsemestres_terminaux = {} formsemestres_terminaux = {}
for trajectoire_aggr in self.trajectoires.values(): for trajectoire_aggr in self.cursus.values():
trajectoire = trajectoire_aggr[nom_rcs] trajectoire = trajectoire_aggr[nom_rcs]
if trajectoire: if trajectoire:
# Le semestre terminal de l'étudiant de l'aggrégat # Le semestre terminal de l'étudiant de l'aggrégat
@ -338,7 +332,7 @@ class EtudiantsJuryPE:
""" """
nbres_semestres = [] nbres_semestres = []
for etudid in etudids: for etudid in etudids:
nbres_semestres.append(self.trajectoires[etudid]["nb_semestres"]) nbres_semestres.append(self.cursus[etudid]["nb_semestres"])
if not nbres_semestres: if not nbres_semestres:
return 0 return 0
return max(nbres_semestres) return max(nbres_semestres)
@ -359,7 +353,7 @@ class EtudiantsJuryPE:
for etudid in etudids: for etudid in etudids:
etudiant = self.identites[etudid] etudiant = self.identites[etudid]
cursus = self.trajectoires[etudid] cursus = self.cursus[etudid]
formsemestres = cursus["formsemestres"] formsemestres = cursus["formsemestres"]
if cursus["diplome"]: if cursus["diplome"]:
@ -549,9 +543,9 @@ def arret_de_formation(etud: Identite, cosemestres: dict[int, FormSemestre]) ->
non_inscrit_a = [ non_inscrit_a = [
rang for rang in etat_inscriptions if not etat_inscriptions[rang] rang for rang in etat_inscriptions if not etat_inscriptions[rang]
] ]
affichage = ",".join([f"S{val}" for val in non_inscrit_a]) affichage = ", ".join([f"S{val}" for val in non_inscrit_a])
pe_affichage.pe_print( 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 return est_demissionnaire
@ -593,26 +587,6 @@ def arret_de_formation(etud: Identite, cosemestres: dict[int, FormSemestre]) ->
# return False # 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( def etapes_du_cursus(
semestres: dict[int, FormSemestre], nbre_etapes_max: int semestres: dict[int, FormSemestre], nbre_etapes_max: int
) -> list[str]: ) -> list[str]:
@ -679,4 +653,3 @@ def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str:
description.append(f"({semestre.formsemestre_id})") description.append(f"({semestre.formsemestre_id})")
return " ".join(description) return " ".join(description)

View File

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

View File

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

View File

@ -1,9 +1,7 @@
import app.pe.pe_comp
import app.pe.rcss.pe_rcf as pe_rcf from app.pe.rcss import pe_rcs, pe_trajectoires, pe_rcsemx
import app.pe.rcss.pe_rcrcf as pe_rcrcf
import app.pe.pe_etudiant as pe_etudiant import app.pe.pe_etudiant as pe_etudiant
import app.pe.pe_comp as pe_comp import app.pe.pe_comp as pe_comp
import app.pe.rcss.rcss_constantes as rcss_constantes
from app.models import FormSemestre from app.models import FormSemestre
from app.pe import pe_affichage from app.pe import pe_affichage
@ -20,159 +18,198 @@ class RCSsJuryPE:
self.annee_diplome = annee_diplome self.annee_diplome = annee_diplome
"""Année de diplômation""" """Année de diplômation"""
self.rcss: dict[tuple(int, str): pe_rcf.RCF] = {} self.trajectoires: dict[tuple(int, str) : pe_trajectoires.Trajectoire] = {}
"""Ensemble des RCS recensés""" """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, """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] = {} self.semXs: dict[tuple(int, str) : pe_trajectoires.SemX] = {}
"""Ensemble des RCF recensés : {(nom_RCS, fid_terminal): RCF}""" """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, """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] = {} def cree_trajectoires(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCRCF}""" """Créé toutes les trajectoires, au regard du cursus des étudiants
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
analysés + les mémorise dans les données de l'étudiant analysés + les mémorise dans les données de l'étudiant
Args: Args:
etudiants: Les étudiants à prendre en compte dans le Jury PE 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 tous_les_aggregats = (
for etudid in etudiants.trajectoires: pe_rcs.TOUS_LES_SEMESTRES + pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
self.rcss_suivis[etudid] = { )
aggregat: None for etudid in etudiants.cursus:
for aggregat in tous_les_aggregats self.trajectoires_suivies[etudid] = {
aggregat: None for aggregat in tous_les_aggregats
} }
for nom_rcs 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 # 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) # 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] 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 # Le formsemestre terminal (dernier en date) associé au
# semestre marquant la fin de l'aggrégat # semestre marquant la fin de l'aggrégat
# (par ex: son dernier S3 en date) # (par ex: son dernier S3 en date)
trajectoire = etudiants.trajectoires[etudid][nom_semestre_terminal] trajectoire = etudiants.cursus[etudid][nom_semestre_terminal]
if trajectoire: 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é # Ajout ou récupération du RCS associé
rcs_id = (nom_rcs, formsemestre_final.formsemestre_id) rcs_id = (nom_rcs, formsemestre_final.formsemestre_id)
if rcs_id not in self.rcss: if rcs_id not in self.trajectoires:
self.rcss[rcs_id] = pe_rcf.RCF(nom_rcs, formsemestre_final) self.trajectoires[rcs_id] = pe_trajectoires.Trajectoire(
rcs = self.rcss[rcs_id] nom_rcs, formsemestre_final
)
rcs = self.trajectoires[rcs_id]
# La liste des semestres de l'étudiant à prendre en compte # La liste des semestres de l'étudiant à prendre en compte
# pour cette trajectoire # pour cette trajectoire
semestres_a_aggreger = get_rcs_etudiant( 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 # 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 # 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 # Affichage pour debug
jeunes = list(enumerate(self.rcss_suivis)) jeunes = list(enumerate(self.trajectoires_suivies))
for no_etud, etudid in jeunes[:20]: for no_etud, etudid in jeunes[:20]:
pe_affichage.pe_print(f"--> {etudiants.identites[etudid].nomprenom} (#{etudid}) :") pe_affichage.pe_print(
for nom_rcs, rcs in self.rcss_suivis[etudid].items(): f"--> {etudiants.identites[etudid].nomprenom} (#{etudid}) :"
)
for nom_rcs, rcs in self.trajectoires_suivies[etudid].items():
if rcs: if rcs:
pe_affichage.pe_print(f" > RCS {nom_rcs}: {rcs.get_repr()}") pe_affichage.pe_print(f" > RCS {nom_rcs}: {rcs.get_repr()}")
def cree_rcfs(self, etudiants: pe_etudiant.EtudiantsJuryPE): def cree_semxs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé les RCFs en ne conservant dans les RCS que les regroupements """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""" de type Sx"""
self.rcfs = {} self.semXs = {}
for rcs_id, rcs in self.rcss.items(): for rcs_id, trajectoire in self.trajectoires.items():
if rcs and rcs.nom in rcss_constantes.TOUS_LES_SEMESTRES: if trajectoire and trajectoire.nom in pe_rcs.TOUS_LES_SEMESTRES:
self.rcfs[rcs_id] = rcs self.semXs[rcs_id] = pe_trajectoires.SemX(trajectoire)
for etudid in self.rcss_suivis: self.semXs_suivis = {}
for nom_rcs, rcs in self.rcss_suivis[etudid].items(): for etudid in self.trajectoires_suivies:
if rcs and nom_rcs in rcss_constantes.TOUS_LES_SEMESTRES: self.semXs_suivis[etudid] = {
if etudid not in self.rcfs_suivis: nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES
self.rcfs_suivis[etudid] = {} }
self.rcfs_suivis[etudid][nom_rcs] = rcs
# Affichage pour debug for nom_rcs, trajectoire in self.trajectoires_suivies[etudid].items():
jeunes = list(enumerate(self.rcfs_suivis)) 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]: for no_etud, etudid in jeunes[:20]:
pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :") 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: 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: 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): def cree_rcsemxs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé tous les RCRCF, au regard du cursus des étudiants """Créé tous les RCSemXs, au regard du cursus des étudiants
analysés (trajectoires traduisant son parcours dans les analysés (trajectoires traduisant son parcours dans les
différents semestres) + les mémorise dans les données de l'étudiant 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 # Pour tous les étudiants du jury
for etudid in self.rcss_suivis: for etudid in self.trajectoires_suivies:
self.rcrcfs_suivis[etudid] = {} self.rcsemxs_suivis[etudid] = {}
for nom_rcs, rcf in self.rcfs_suivis[etudid].items(): # Pour chaque RCS # Recopie des SemX & des suivis associés
semestres_a_aggreger = rcf.semestres_aggreges 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 # Pour chaque aggréggat de type xA ou Sx
semestres_tries = pe_comp.tri_semestres_par_rang(semestres_a_aggreger) for nom_rcs in pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM:
trajectoire = self.trajectoires_suivies[etudid][nom_rcs]
# Récupére les RCFs de type Sx traduisant sa trajectoire if not trajectoire:
rcfs_a_aggreger = {} self.rcsemxs_suivis[etudid][nom_rcs] = None
for semestres_du_rang in semestres_tries.values(): else:
if semestres_du_rang: # Identifiant de la trajectoire => donnera ceux du RCSemX
rcf_id = get_rcf_from_semestres_aggreges( tid = trajectoire.rcs_id
self.rcfs, semestres_du_rang # 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( # Récupére les SemX (RC de type Sx) associés aux semestres de son cursus
"Il manque un RCF pour créer les RCRCFs dans cree_rcrcfs" # 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 # Ajout des SemX à ceux à aggréger dans le RCSemX
if rcf_id not in self.rcrcfs: rcsemx = self.rcsemxs[tid]
rcf_nom = rcf_id[0] rcsemx.add_semXs(semxs_a_aggreger)
self.rcrcfs[rcf_id] = pe_rcrcf.RCRCF(rcf_nom, rcf.formsemestre_final)
rcrcf = self.rcrcfs[rcf_id]
# Ajout des RCFs au RCRCF # Mémoire du RCSemX aux informations de suivi de l'étudiant
rcrcf.add_rcfs_a_aggreger(rcfs_a_aggreger) self.rcsemxs_suivis[etudid][nom_rcs] = rcsemx
# Mémoire la trajectoire RCRCF suivie par l'étudiant
nom_rcs = rcrcf.nom
self.rcrcfs_suivis[etudid][nom_rcs] = rcrcf
def _aff_rcsemxs_suivis(self, etudiants):
"""Affiche les RCSemX suivis par les étudiants"""
# Affichage pour debug # Affichage pour debug
jeunes = list(enumerate(self.rcrcfs_suivis)) jeunes = list(enumerate(self.rcsemxs_suivis))
vides = []
for no_etud, etudid in jeunes[:20]: for no_etud, etudid in jeunes[:20]:
pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :") 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: 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: 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( def get_rcs_etudiant(
@ -204,7 +241,7 @@ def get_rcs_etudiant(
numero_semestres_possibles = [numero_semestre_terminal] numero_semestres_possibles = [numero_semestre_terminal]
elif nom_rcs.endswith("A"): # les années elif nom_rcs.endswith("A"): # les années
numero_semestres_possibles = [ 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 assert numero_semestre_terminal in numero_semestres_possibles
else: # les xS = tous les semestres jusqu'à Sx (eg S1, S2, S3 pour un S3 terminal) 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 return semestres_aggreges
def get_rcf_from_semestres_aggreges( def get_semx_from_semestres_aggreges(
rcfs: dict[(str, int):pe_rcf.RCF], semestres_a_aggreges: list[FormSemestre] semXs: dict[(str, int) : pe_trajectoires.SemX],
) -> (str, int): semestres_a_aggreger: dict[(str, int):FormSemestre],
"""Partant d'un dictionnaire de RCFs (de la forme ) -> (str, int):
``{ (nom_rcs, fid): RCF }, et connaissant une liste """Partant d'un dictionnaire de SemX (de la forme
de (form)semestres à aggréger, renvoie l'identifiant ``{ (nom_rcs, fid): SemX }, et connaissant une liste
(nom_rcs, fid) du RCFs qui lui correspond (c'est à dire celui dont de (form)semestres suivis, renvoie l'identifiant
les semestres_aggregés par le RCF sont les même que les (nom_rcs, fid) du SemX qui lui correspond.
semestres_a_aggreger.
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: Returns:
rcf_id: L'identifiant du RCF trouvé rcf_id: L'identifiant du RCF trouvé
""" """
fids_semestres_a_aggreger = set( assert semestres_a_aggreger, "Pas de semestres à aggréger"
[frms.formsemestre_id for frms in semestres_a_aggreges] rangs_a_aggreger = [sem.semestre_id for fid, sem in semestres_a_aggreger.items()]
) assert (
for rcf_id, rcf in rcfs.items(): len(set(rangs_a_aggreger)) == 1
fids_rcf = set(rcf.semestres_aggreges) ), "Tous les sem à aggréger doivent être de même rang"
if fids_rcf == fids_semestres_a_aggreger:
return rcf_id
return None
# 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 pandas as pd
import numpy as np import numpy as np
import app.pe.rcss.pe_rcs as pe_rcs 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_sxtag as pe_sxtag
import app.pe.pe_comp as pe_comp import app.pe.pe_comp as pe_comp
from app.pe.pe_tabletags import TableTag from app.pe.pe_tabletags import TableTag
@ -66,7 +66,7 @@ class RCSTag(TableTag):
self.rcs_id: tuple(str, int) = rcrcf.rcs_id self.rcs_id: tuple(str, int) = rcrcf.rcs_id
"""Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)""" """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é""" """RCRCF associé au RCS taggué"""
self.nom = self.get_repr() self.nom = self.get_repr()
@ -82,12 +82,12 @@ class RCSTag(TableTag):
pe_affichage.pe_print(f"-> {self.get_repr(verbose=True)}") pe_affichage.pe_print(f"-> {self.get_repr(verbose=True)}")
# Les données aggrégés (RCRCF + SxTags # 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""" """Les RCFs aggrégés"""
self.sxstags = {} self.sxstags = {}
"""Les SxTag associés aux RCF aggrégés""" """Les SxTag associés aux RCF aggrégés"""
try: try:
for rcf_id in self.rcfs_aggreges: for rcf_id in self.rcsemxs_aggreges:
self.sxstags[rcf_id] = sxstags[rcf_id] self.sxstags[rcf_id] = sxstags[rcf_id]
except: except:
raise ValueError("Semestres SxTag manquants") 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_tabletags import TableTag
from app.pe.pe_moytag import MoyennesTag 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): class SxTag(TableTag):
def __init__( def __init__(
self, self,
sxtag_id: (str, int), sxtag_id: (str, int),
rcf: pe_rcf.RCF, semx: pe_trajectoires.SemX,
ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag], ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag],
): ):
"""Calcule les moyennes/classements par tag d'un semestre de type 'Sx' """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 self.sxtag_id: (str, int) = sxtag_id
"""Identifiant du SxTag de la forme (nom_Sx, fid_semestre_final)""" """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 self.nom_rcs = sxtag_id[0]
"""Le RCF sur lequel il s'appuie"""
assert rcf.rcs_id == sxtag_id, "Problème de correspondance SxTag/RCF"
# Les resultats des semestres taggués à prendre en compte dans le RCF self.semx = semx
self.ressembuttags = {fid: ressembuttags[fid] for fid in rcf.semestres_aggreges} """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 ResSemBUTTags à regrouper dans le SxTag"""
# Les données du semestre final # Les données du semestre final
@ -108,7 +117,7 @@ class SxTag(TableTag):
"""Les etudids triés""" """Les etudids triés"""
# Affichage # Affichage
pe_affichage.pe_print(f"--> {self.get_repr()}") pe_affichage.pe_print(f"--> {self.get_repr(verbose=True)}")
# Les tags # Les tags
self.tags_sorted = self.ressembuttag_final.tags_sorted 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 """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
est basée)""" est basée)"""
if verbose: 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: else:
# affichage = [str(fid) for fid in self.ressembuttags] # 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): def _aff_capitalisations(self):
"""Affichage des capitalisations du sxtag pour debug""" """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 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: def get_descr_rcs(nom_rcs: str) -> str:
"""Renvoie la description pour les tableurs de synthèse """Renvoie la description pour les tableurs de synthèse
Excel d'un nom de RCS""" Excel d'un nom de RCS"""
return rcss_constantes.TYPES_RCS[nom_rcs]["descr"] return TYPES_RCS[nom_rcs]["descr"]
class RCS: 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): def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
self.nom: str = nom_rcs self.nom: str = nom_rcs
"""Nom du 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 self.formsemestre_final: FormSemestre = semestre_final
"""FormSemestre terminal du RCS""" """(Form)Semestre final du RCS"""
self.rang_final = self.formsemestre_final.semestre_id 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) self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id)
"""Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)""" """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: def get_formsemestre_id_final(self) -> int:
"""Renvoie l'identifiant du formsemestre final du RCS """Renvoie l'identifiant du formsemestre final du RCS
@ -58,5 +126,3 @@ class RCS:
self.nom == other.nom self.nom == other.nom
and self.formsemestre_final == other.formsemestre_final 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")]