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
14 changed files with 658 additions and 450 deletions
Showing only changes of commit d6a75b176e - Show all commits

View File

@ -41,13 +41,13 @@ import datetime
import re import re
import unicodedata import unicodedata
import pandas as pd
from flask import g 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.pe_rcs import TYPES_RCS from app.pe.rcss.rcss_constantes 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
@ -291,6 +291,29 @@ def tri_semestres_par_rang(cosemestres: dict[int, FormSemestre]):
dictionnaire {rang: [liste des semestres du dit rang]}""" dictionnaire {rang: [liste des semestres du dit rang]}"""
cosemestres_tries = {} cosemestres_tries = {}
for sem in cosemestres.values(): for sem in cosemestres.values():
cosemestres_tries[sem.semestre_id] = cosemestres_tries.get(sem.semestre_id, []) + [sem] cosemestres_tries[sem.semestre_id] = cosemestres_tries.get(
sem.semestre_id, []
) + [sem]
return cosemestres_tries return cosemestres_tries
def find_index_and_columns_communs(
df1: pd.DataFrame, df2: pd.DataFrame
) -> (list, list):
"""Partant de 2 DataFrames ``df1`` et ``df2``, renvoie les indices de lignes
et de colonnes, communes aux 2 dataframes
Args:
df1: Un dataFrame
df2: Un dataFrame
Returns:
Le tuple formé par la liste des indices de lignes communs et la liste des indices
de colonnes communes entre les 2 dataFrames
"""
indices1 = df1.index
indices2 = df2.index
indices_communs = list(df1.index.intersection(df2.index))
colonnes1 = df1.columns
colonnes2 = df2.columns
colonnes_communes = list(set(colonnes1) & set(colonnes2))
return indices_communs, colonnes_communes

View File

@ -38,9 +38,10 @@ 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
from app.pe.pe_etudiant import EtudiantsJuryPE import app.pe.pe_etudiant as pe_etudiant
from app.pe.pe_rcs import RCS, RCSsJuryPE import app.pe.rcss.pe_rcs as pe_rcs
from app.pe.pe_rcstag import RCSTag import app.pe.pe_rcss_jury as pe_rcss_jury
import app.pe.pe_rcstag as pe_rcstag
class RCSInterclasseTag(TableTag): class RCSInterclasseTag(TableTag):
@ -58,9 +59,9 @@ class RCSInterclasseTag(TableTag):
def __init__( def __init__(
self, self,
nom_rcs: str, nom_rcs: str,
etudiants: EtudiantsJuryPE, etudiants: pe_etudiant.EtudiantsJuryPE,
rcss_jury_pe: RCSsJuryPE, rcss_jury_pe: pe_rcss_jury.RCSsJuryPE,
rcss_tags: dict[tuple, RCSTag], rcss_tags: dict[tuple, pe_rcstag.RCSTag],
): ):
TableTag.__init__(self) TableTag.__init__(self)
@ -80,21 +81,21 @@ class RCSInterclasseTag(TableTag):
# Les trajectoires (et leur version tagguées), en ne gardant que # Les trajectoires (et leur version tagguées), en ne gardant que
# celles associées à l'aggrégat # celles associées à l'aggrégat
self.rcss: dict[int, 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.rcss:
trajectoire = rcss_jury_pe.rcss[trajectoire_id] trajectoire = rcss_jury_pe.rcss[trajectoire_id]
if trajectoire_id[0] == nom_rcs: if trajectoire_id[0] == nom_rcs:
self.rcss[trajectoire_id] = trajectoire self.rcss[trajectoire_id] = trajectoire
self.trajectoires_taggues: dict[int, RCS] = {} self.trajectoires_taggues: dict[int, pe_rcs.RCS] = {}
"""Ensemble des trajectoires tagguées associées à l'aggrégat""" """Ensemble des trajectoires tagguées associées à l'aggrégat"""
for trajectoire_id in self.rcss: for trajectoire_id in self.rcss:
self.trajectoires_taggues[trajectoire_id] = rcss_tags[trajectoire_id] self.trajectoires_taggues[trajectoire_id] = rcss_tags[trajectoire_id]
# Les trajectoires suivies par les étudiants du jury, en ne gardant que # Les trajectoires suivies par les étudiants du jury, en ne gardant que
# celles associées aux diplomés # celles associées aux diplomés
self.suivi: dict[int, RCS] = {} self.suivi: dict[int, pe_rcs.RCS] = {}
"""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:

View File

@ -50,15 +50,16 @@ 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 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.pe_rcs as pe_rcs 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_comp as pe_comp import app.pe.pe_rcss_jury as pe_rcss_jury
class JuryPE(object): class JuryPE(object):
@ -94,7 +95,7 @@ class JuryPE(object):
self.etudiants.find_etudiants() self.etudiants.find_etudiants()
self.diplomes_ids = self.etudiants.diplomes_ids self.diplomes_ids = self.etudiants.diplomes_ids
self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome) self.rcss_jury = pe_rcss_jury.RCSsJuryPE(self.diplome)
"""Les informations sur les regroupements de semestres""" """Les informations sur les regroupements de semestres"""
self.zipdata = io.BytesIO() self.zipdata = io.BytesIO()
@ -102,8 +103,8 @@ class JuryPE(object):
if not self.diplomes_ids: if not self.diplomes_ids:
pe_affichage.pe_print("*** Aucun étudiant diplômé") pe_affichage.pe_print("*** Aucun étudiant diplômé")
else: else:
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_rcss()
self._gen_xls_sxtags(zipfile) self._gen_xls_sxtags(zipfile)
@ -112,6 +113,8 @@ class JuryPE(object):
# 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)
except Exception as e:
raise e
# et le log # et le log
self._add_log_to_zip(zipfile) self._add_log_to_zip(zipfile)
@ -186,14 +189,15 @@ class JuryPE(object):
) )
self.rcss_jury.cree_rcss(self.etudiants) self.rcss_jury.cree_rcss(self.etudiants)
self.rcss_jury.cree_rcfs(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 RCS de type Sx (pour """Génère les semestres taggués en s'appuyant sur les RCS de type Sx (pour
identifier les redoublements impactant les semestres taggués). identifier les redoublements impactant les semestres taggués).
""" """
# Génère les regroupements de semestres de type Sx # Génère les regroupements de semestres de type Sx
pe_affichage.pe_print("*** Génère les RCF (RCS de type Sx)***") pe_affichage.pe_print(
"*** Génère les RCSValid (RCS de même Sx donnant lieu à validation du semestre)"
)
self.rcss_jury.cree_rcfs(self.etudiants) self.rcss_jury.cree_rcfs(self.etudiants)
# Génère les moyennes des RCS de type Sx # Génère les moyennes des RCS de type Sx
@ -204,11 +208,8 @@ class JuryPE(object):
for rcf_id, rcf in self.rcss_jury.rcfs.items(): for rcf_id, rcf in self.rcss_jury.rcfs.items():
# SxTag traduisant le RCF # SxTag traduisant le RCF
sxtag_id = rcf_id sxtag_id = rcf_id
# Les resultats des semestres taggués à prendre en compte dans le RCF
ressemstags = { self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, rcf, self.ressembuttags)
fid: self.ressembuttags[fid] for fid in rcf.semestres_aggreges
}
self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, ressemstags)
# Intègre le bilan des semestres taggués au zip final # Intègre le bilan des semestres taggués au zip final
output = io.BytesIO() output = io.BytesIO()
@ -232,10 +233,11 @@ class JuryPE(object):
def _gen_rcrcfs(self): def _gen_rcrcfs(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("*** Génère les RCRCF (regroupements de RCF de type Sx) amenant du S1 à un semestre final***") pe_affichage.pe_print(
"*** Génère les RCRCF (regroupements de RCF de type Sx) amenant du S1 à un semestre final***"
)
self.rcss_jury.cree_rcrcfs(self.etudiants) self.rcss_jury.cree_rcrcfs(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)
de regroupements de semestre de type Sx, xA ou xS. de regroupements de semestre de type Sx, xA ou xS.
@ -268,7 +270,7 @@ class JuryPE(object):
output, engine="openpyxl" output, engine="openpyxl"
) as writer: ) as writer:
for rcs_tag in self.rcss_tags.values(): for rcs_tag in self.rcss_tags.values():
onglet = rcs_tag.get_repr(mode="short") onglet = rcs_tag.get_repr(verbose=False)
df = rcs_tag.df_moyennes_et_classements() df = rcs_tag.df_moyennes_et_classements()
# écriture dans l'onglet # écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True) df.to_excel(writer, onglet, index=True, header=True)
@ -276,7 +278,7 @@ class JuryPE(object):
self.add_file_to_zip( self.add_file_to_zip(
zipfile, zipfile,
f"RCS_taggues_{self.diplome}.xlsx", f"RCRCFs_{self.diplome}.xlsx",
output.read(), output.read(),
path="details", path="details",
) )
@ -431,7 +433,7 @@ 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 = pe_rcs.TYPES_RCS[aggregat]["descr"] descr = app.pe.rcss.constantes.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é
@ -632,7 +634,7 @@ def get_formsemestres_etudiants(etudiants: pe_etudiant.EtudiantsJuryPE) -> dict:
def compute_interclassements( def compute_interclassements(
etudiants: pe_etudiant.EtudiantsJuryPE, etudiants: pe_etudiant.EtudiantsJuryPE,
trajectoires_jury_pe: pe_rcs.RCSsJuryPE, trajectoires_jury_pe: pe_rcss_jury.RCSsJuryPE,
trajectoires_tagguees: dict[tuple, pe_rcs.RCS], trajectoires_tagguees: dict[tuple, pe_rcs.RCS],
): ):
"""Interclasse les étudiants, (nom d') aggrégat par aggrégat, """Interclasse les étudiants, (nom d') aggrégat par aggrégat,

View File

@ -181,7 +181,7 @@ class MoyennesTag:
self.matrice_notes: pd.DataFrame = matrice_notes self.matrice_notes: pd.DataFrame = matrice_notes
"""Les notes aux UEs ou aux compétences (DataFrame)""" """Les notes aux UEs ou aux compétences (DataFrame)"""
self.matrice_coeffs: pd.DataFrame = matrice_coeffs self.matrice_coeffs_moy_gen: pd.DataFrame = matrice_coeffs
"""Les coeffs à appliquer pour le calcul des moyennes générales """Les coeffs à appliquer pour le calcul des moyennes générales
(toutes UE ou compétences confondues). NaN si étudiant non inscrit""" (toutes UE ou compétences confondues). NaN si étudiant non inscrit"""
@ -193,7 +193,7 @@ class MoyennesTag:
self.moyennes[col] = Moyenne(notes) self.moyennes[col] = Moyenne(notes)
# Les moyennes générales # Les moyennes générales
notes_gen = self.compute_moy_gen(self.matrice_notes, self.matrice_coeffs) notes_gen = self.compute_moy_gen(self.matrice_notes, self.matrice_coeffs_moy_gen)
self.notes_gen = notes_gen self.notes_gen = notes_gen
"""Les notes générales (moyenne toutes UEs confonudes)""" """Les notes générales (moyenne toutes UEs confonudes)"""
self.moyenne_gen = Moyenne(notes_gen) self.moyenne_gen = Moyenne(notes_gen)

View File

@ -1,248 +1,11 @@
##############################################################################
# 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.rcss.pe_rcf as pe_rcf
import app.pe.rcss.pe_rcrcf as pe_rcrcf
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_sxtag, pe_affichage from app.pe import pe_affichage
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
TYPES_RCS = {
"S1": {
"aggregat": ["S1"],
"descr": "Semestre 1 (S1)",
},
"S2": {
"aggregat": ["S2"],
"descr": "Semestre 2 (S2)",
},
"1A": {
"aggregat": ["S1", "S2"],
"descr": "BUT1 (S1+S2)",
},
"S3": {
"aggregat": ["S3"],
"descr": "Semestre 3 (S3)",
},
"S4": {
"aggregat": ["S4"],
"descr": "Semestre 4 (S4)",
},
"2A": {
"aggregat": ["S3", "S4"],
"descr": "BUT2 (S3+S4)",
},
"3S": {
"aggregat": ["S1", "S2", "S3"],
"descr": "Moyenne du semestre 1 au semestre 3 (S1+S2+S3)",
},
"4S": {
"aggregat": ["S1", "S2", "S3", "S4"],
"descr": "Moyenne du semestre 1 au semestre 4 (S1+S2+S3+S4)",
},
"S5": {
"aggregat": ["S5"],
"descr": "Semestre 5 (S5)",
},
"S6": {
"aggregat": ["S6"],
"descr": "Semestre 6 (S6)",
},
"3A": {
"aggregat": ["S5", "S6"],
"descr": "3ème année (S5+S6)",
},
"5S": {
"aggregat": ["S1", "S2", "S3", "S4", "S5"],
"descr": "Moyenne du semestre 1 au semestre 5 (S1+S2+S3+S4+S5)",
},
"6S": {
"aggregat": ["S1", "S2", "S3", "S4", "S5", "S6"],
"descr": "Moyenne globale (S1+S2+S3+S4+S5+S6)",
},
}
"""Dictionnaire détaillant les différents regroupements cohérents
de semestres (RCS), en leur attribuant un nom et en détaillant
le nom des semestres qu'ils regroupent et l'affichage qui en sera fait
dans les tableurs de synthèse.
"""
TOUS_LES_RCS_AVEC_PLUSIEURS_SEM = [cle for cle in TYPES_RCS if not cle.startswith("S")]
TOUS_LES_RCS = list(TYPES_RCS.keys())
TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")]
def get_descr_rcs(nom_rcs: str) -> str:
"""Renvoie la description pour les tableurs de synthèse
Excel d'un nom de RCS"""
return TYPES_RCS[nom_rcs]["descr"]
class RCS:
"""Modélise un regroupement cohérent de semestres (formsemestre ou de Sx)"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
self.nom: str = nom_rcs
"""Nom du RCS"""
self.formsemestre_final: FormSemestre = semestre_final
"""FormSemestre terminal du RCS"""
self.rang_final = self.formsemestre_final.semestre_id
"""Le 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)"""
def get_formsemestre_id_final(self) -> int:
"""Renvoie l'identifiant du formsemestre final du RCS
Returns:
L'id du formsemestre final (marquant la fin) du RCS
"""
return self.formsemestre_final.formsemestre_id
def __repr__(self):
"""Représentation textuelle d'un RCS"""
return f"{self.nom} ({self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"
def get_repr(self):
return self.__repr__()
def __eq(self, other):
"""Egalité de RCS"""
return (
self.nom == other.nom
and self.formsemestre_final == other.formsemestre_final
)
class RCF(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):
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"""
noms = []
for fid in self.semestres_aggreges:
semestre = self.semestres_aggreges[fid]
noms.append(f"S{semestre.semestre_id}({fid})")
noms = sorted(noms)
title = f"""{str(self)}"""
if verbose and noms:
title += " - " + "+".join(noms)
return title
def get_rcf_from_semestres_aggreges(
rcfs: dict[(str, int):RCF], semestres_a_aggreges: list[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.
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
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):
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):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"""
noms = []
for rcf_id, rcf in self.rcfs_aggreges.items():
noms.append(rcf.get_repr())
title = f"""{str(self)}"""
if verbose and noms:
title += " : " + "+".join(noms)
return title
class RCSsJuryPE: class RCSsJuryPE:
@ -257,28 +20,28 @@ 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) : RCF] = {} self.rcss: dict[tuple(int, str): pe_rcf.RCF] = {}
"""Ensemble des RCS recensés""" """Ensemble des RCS recensés"""
self.rcss_suivis: dict[int:dict] = {} self.rcss_suivis: 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}}""" son RCS : {etudid: {nom_RCS: RCS}}"""
self.rcfs: dict[tuple(int, str) : RCF] = {} self.rcfs: dict[tuple(int, str) : pe_rcf.RCF] = {}
"""Ensemble des RCF recensés : {(nom_RCS, fid_terminal): RCF}""" """Ensemble des RCF recensés : {(nom_RCS, fid_terminal): RCF}"""
self.rcfs_suivis: dict[int:dict] = {} self.rcfs_suivis: 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: RCF}}""" son RCS : {etudid: {nom_RCS: RCF}}"""
self.rcrcfs: dict[tuple(int, str) : RCRCF] = {} self.rcrcfs: dict[tuple(int, str) : pe_rcrcf.RCRCF] = {}
"""Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCRCF}""" """Ensemble des RCS recensés : {(nom_RCS, fid_terminal): RCRCF}"""
self.rcrcfs_suivis: dict[int:str] = {} self.rcrcfs_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 RCRCF : {etudid: {nom_RCS: RCSx}}""" son RCRCF : {etudid: {nom_RCS: RCSx}}"""
def cree_rcss(self, etudiants: EtudiantsJuryPE): def cree_rcss(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé tous les RCS, au regard du cursus des étudiants """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
@ -286,17 +49,17 @@ class RCSsJuryPE:
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
for etudid in etudiants.trajectoires: for etudid in etudiants.trajectoires:
self.rcss_suivis[etudid] = { self.rcss_suivis[etudid] = {
aggregat: None aggregat: None
for aggregat in pe_comp.TOUS_LES_SEMESTRES for aggregat in tous_les_aggregats
+ TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
} }
for nom_rcs in pe_comp.TOUS_LES_SEMESTRES + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM: 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 = TYPES_RCS[nom_rcs]["aggregat"] noms_semestre_de_aggregat = rcss_constantes.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.trajectoires:
@ -305,12 +68,12 @@ class RCSsJuryPE:
# (par ex: son dernier S3 en date) # (par ex: son dernier S3 en date)
trajectoire = etudiants.trajectoires[etudid][nom_semestre_terminal] trajectoire = etudiants.trajectoires[etudid][nom_semestre_terminal]
if trajectoire: if trajectoire:
formsemestre_final = get_dernier_semestre_en_date(trajectoire) formsemestre_final = pe_etudiant.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.rcss:
self.rcss[rcs_id] = RCF(nom_rcs, formsemestre_final) self.rcss[rcs_id] = pe_rcf.RCF(nom_rcs, formsemestre_final)
rcs = self.rcss[rcs_id] rcs = self.rcss[rcs_id]
# La liste des semestres de l'étudiant à prendre en compte # La liste des semestres de l'étudiant à prendre en compte
@ -328,23 +91,22 @@ class RCSsJuryPE:
# Affichage pour debug # Affichage pour debug
jeunes = list(enumerate(self.rcss_suivis)) jeunes = list(enumerate(self.rcss_suivis))
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} (#{etudid}) :")
for nom_rcs, rcs in self.rcss_suivis[etudid].items(): for nom_rcs, rcs in self.rcss_suivis[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: EtudiantsJuryPE): def cree_rcfs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé les RCFs en ne conservant dans les RCS que les regroupements """Créé les RCFs en ne conservant dans les RCS que les regroupements
de type Sx""" de type Sx"""
self.rcfs = {} self.rcfs = {}
for rcs_id, rcs in self.rcss.items(): for rcs_id, rcs in self.rcss.items():
if rcs and rcs.nom in pe_comp.TOUS_LES_SEMESTRES: if rcs and rcs.nom in rcss_constantes.TOUS_LES_SEMESTRES:
self.rcfs[rcs_id] = rcs self.rcfs[rcs_id] = rcs
print(self.rcfs)
for etudid in self.rcss_suivis: for etudid in self.rcss_suivis:
for nom_rcs, rcs in self.rcss_suivis[etudid].items(): for nom_rcs, rcs in self.rcss_suivis[etudid].items():
if rcs and nom_rcs in pe_comp.TOUS_LES_SEMESTRES: if rcs and nom_rcs in rcss_constantes.TOUS_LES_SEMESTRES:
if etudid not in self.rcfs_suivis: if etudid not in self.rcfs_suivis:
self.rcfs_suivis[etudid] = {} self.rcfs_suivis[etudid] = {}
self.rcfs_suivis[etudid][nom_rcs] = rcs self.rcfs_suivis[etudid][nom_rcs] = rcs
@ -355,11 +117,11 @@ class RCSsJuryPE:
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.rcfs_suivis[etudid].items():
if rcs: if rcs:
pe_affichage.pe_print(f" > RCF {nom_rcs}: {rcs.get_repr()}") pe_affichage.pe_print(f" > RCSValid {nom_rcs}: {rcs.get_repr()}")
else: else:
pe_affichage.pe_print(f" > RCF {nom_rcs}: <vide> !!! ") pe_affichage.pe_print(f" > RCSValid {nom_rcs}: <vide>")
def cree_rcrcfs(self, etudiants: EtudiantsJuryPE): def cree_rcrcfs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
"""Créé tous les RCRCF, au regard du cursus des étudiants """Créé tous les RCRCF, 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
@ -390,7 +152,7 @@ class RCSsJuryPE:
# Ajout du RCRCF # Ajout du RCRCF
if rcf_id not in self.rcrcfs: if rcf_id not in self.rcrcfs:
self.rcrcfs[rcf_id] = RCRCF(rcf_id, rcf.formsemestre_final) self.rcrcfs[rcf_id] = pe_rcrcf.RCRCF(rcf_id, rcf.formsemestre_final)
rcrcf = self.rcrcfs[rcf_id] rcrcf = self.rcrcfs[rcf_id]
# Ajout des RCFs au RCRCF # Ajout des RCFs au RCRCF
@ -410,6 +172,8 @@ class RCSsJuryPE:
else: else:
pe_affichage.pe_print(f" > RCRCF {nom_rcs}: <vide> !!! ") pe_affichage.pe_print(f" > RCRCF {nom_rcs}: <vide> !!! ")
def get_rcs_etudiant( def get_rcs_etudiant(
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
) -> dict[int, FormSemestre]: ) -> dict[int, FormSemestre]:
@ -439,7 +203,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 TYPES_RCS[nom_rcs]["aggregat"] int(sem[-1]) for sem in rcss_constantes.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)
@ -454,3 +218,27 @@ def get_rcs_etudiant(
): ):
semestres_aggreges[fid] = semestre semestres_aggreges[fid] = semestre
return semestres_aggreges return semestres_aggreges
def get_rcf_from_semestres_aggreges(
rcfs: dict[(str, int):pe_rcf.RCF], semestres_a_aggreges: list[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.
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

View File

@ -38,18 +38,18 @@ Created on Fri Sep 9 09:15:05 2016
from app.comp.res_sem import load_formsemestre_results from app.comp.res_sem import load_formsemestre_results
from app.pe import pe_affichage from app.pe import pe_affichage
from app.pe.pe_ressemtag import ResSemBUTTag
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from app.pe.pe_rcs import RCS, RCRCF import app.pe.rcss.pe_rcs as pe_rcs
from app.pe.pe_sxtag import SxTag import app.pe.rcss.pe_rcrcf as pe_rcrcf
import app.pe.pe_sxtag as pe_sxtag
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
class RCSTag(TableTag): class RCSTag(TableTag):
def __init__(self, rcrcf: RCS, sxstags: dict[(str, int): SxTag]): def __init__(self, rcrcf: pe_rcs.RCS, sxstags: dict[(str, int): pe_sxtag.SxTag]):
"""Calcule les moyennes par tag (orientées compétences) """Calcule les moyennes par tag (orientées compétences)
d'un regroupement de SxTag d'un regroupement de SxTag
(RCRCF), pour extraire les classements par tag pour un (RCRCF), pour extraire les classements par tag pour un
@ -65,7 +65,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: RCRCF = rcrcf self.rcrcf: pe_rcrcf.RCRCF = rcrcf
"""RCRCF associé au RCS taggué""" """RCRCF associé au RCS taggué"""
self.nom = self.get_repr() self.nom = self.get_repr()
@ -74,6 +74,8 @@ class RCSTag(TableTag):
self.formsemestre_terminal = rcrcf.formsemestre_final self.formsemestre_terminal = rcrcf.formsemestre_final
"""Le formsemestre terminal""" """Le formsemestre terminal"""
pe_affichage.pe_print(f"-> {self.get_repr(verbose=True)}")
# Les résultats du formsemestre terminal # Les résultats du formsemestre terminal
nt = load_formsemestre_results(self.formsemestre_terminal) nt = load_formsemestre_results(self.formsemestre_terminal)
@ -95,10 +97,12 @@ class RCSTag(TableTag):
# Les compétences (extraites de tous les Sxtags) # Les compétences (extraites de tous les Sxtags)
self.competences_sorted = self.do_complist() self.competences_sorted = self.do_complist()
"""Compétences extraites de tous les SxTag aggrégés""" """Compétences extraites de tous les SxTag aggrégés"""
pe_affichage.pe_print(f"* Compétences : {', '.join(self.competences_sorted)}")
# Les tags # Les tags
self.tags_sorted = self.do_taglist() self.tags_sorted = self.do_taglist()
"""Tags extraits de tous les SxTag aggrégés""" """Tags extraits de tous les SxTag aggrégés"""
pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}")
# Les moyennes # Les moyennes
self.moyennes_tags: dict[str, MoyennesTag] = {} self.moyennes_tags: dict[str, MoyennesTag] = {}
@ -121,10 +125,13 @@ class RCSTag(TableTag):
"""Egalité de 2 RCS taggués sur la base de leur identifiant""" """Egalité de 2 RCS taggués sur la base de leur identifiant"""
return self.rcs_id == other.sxtag_id return self.rcs_id == other.sxtag_id
def get_repr(self, verbose=False) -> str: def get_repr(self, verbose=True) -> str:
"""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:
return self.rcrcf.get_repr(verbose=verbose) return self.rcrcf.get_repr(verbose=verbose)
else:
return f"{self.__class__.__name__} ({self.rcs_id})"
def compute_notes_comps_cube(self, tag): def compute_notes_comps_cube(self, tag):
"""Pour un tag donné, construit : """Pour un tag donné, construit :
@ -154,10 +161,11 @@ class RCSTag(TableTag):
# Charge les notes et les coeffs du semestre tag # Charge les notes et les coeffs du semestre tag
notes = moys_tag.matrice_notes.copy() # avec une copie notes = moys_tag.matrice_notes.copy() # avec une copie
coeffs = moys_tag.matrice_coeffs.copy() # les coeffs coeffs = moys_tag.matrice_coeffs_moy_gen.copy() # les coeffs
# Traduction des UE en compétences # Traduction des UE en compétences
association_ues_comp = moys_tag.competences association_ues_comp = self.mapping_ue_competences()
print(association_ues_comp)
ues_columns_df = notes.columns ues_columns_df = notes.columns
comp_associes_aux_ues = [association_ues_comp[ue] for ue in ues_columns_df] comp_associes_aux_ues = [association_ues_comp[ue] for ue in ues_columns_df]
notes.columns = comp_associes_aux_ues notes.columns = comp_associes_aux_ues
@ -204,16 +212,20 @@ class RCSTag(TableTag):
tags = [] tags = []
for frmsem_id in self.sxstags: for frmsem_id in self.sxstags:
tags.extend(self.sxstags[frmsem_id].tags_sorted) tags.extend(self.sxstags[frmsem_id].tags_sorted)
pe_affichage.pe_print(f"* Tags : {', '.join(tags)}")
return sorted(set(tags)) return sorted(set(tags))
def mapping_ue_competences(self):
"""Dictionnaire {ue: competences} extrait des SxTags"""
dict_competences = {}
for sxtag_id, sxtag in self.sxstags.items():
comp = sxtag.competences
dict_competences |= comp
return dict_competences
def do_complist(self): def do_complist(self):
"""Synthétise les compétences à partir des Sxtags aggrégés""" """Synthétise les compétences à partir des Sxtags aggrégés"""
competences = [] dict_comptences = self.mapping_ue_competences()
for sxtag_id, sxtag in self.sxstags.items(): return sorted(set(dict_comptences.values()))
comp = list(sxtag.moyennes_tags["but"].matrice_notes.columns)
competences.extend(comp)
return sorted(set(competences))
def compute_notes_competences( def compute_notes_competences(
@ -248,7 +260,7 @@ def compute_notes_competences(
# Enlève les NaN du cube de notes pour les entrées manquantes # Enlève les NaN du cube de notes pour les entrées manquantes
set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0) set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0)
coeffs_cube_no_nan = no.nan_to_num(coeff_cube, nan=0.0) coeffs_cube_no_nan = np.nan_to_num(coeff_cube, nan=0.0)
# Les moyennes par tag # Les moyennes par tag
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)

View File

@ -48,7 +48,7 @@ import app.pe.pe_etudiant as pe_etudiant
import app.pe.pe_tabletags as pe_tabletags import app.pe.pe_tabletags as pe_tabletags
from app.pe.pe_moytag import MoyennesTag from app.pe.pe_moytag import MoyennesTag
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.scodoc.codes_cursus import UE_SPORT from app.scodoc import codes_cursus as sco_codes
class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
@ -66,44 +66,51 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
ResultatsSemestreBUT.__init__(self, formsemestre) ResultatsSemestreBUT.__init__(self, formsemestre)
pe_tabletags.TableTag.__init__(self) pe_tabletags.TableTag.__init__(self)
# Le semestre
# self.formsemestre_id = self.formsemestre.formsemestre_id
# self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Le nom du res_semestre taggué # Le nom du res_semestre taggué
self.nom = self.get_repr(verbose=True) self.nom = self.get_repr(verbose=True)
pe_affichage.pe_print(f"--> Résultats de semestre taggués {self.nom}") pe_affichage.pe_print(f"--> Résultats de semestre taggués {self.nom}")
# Les résultats du semestre
# self.nt = load_formsemestre_results(self.formsemestre)
# Les étudiants (etuds, états civils & etudis) ajouté # Les étudiants (etuds, états civils & etudis) ajouté
self.add_etuds(self.etuds) self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids)
# Les UEs (et les dispenses d'UE) # Les UEs (et les dispenses d'UE)
ues_hors_sport = [ue for ue in self.ues if ue.type != UE_SPORT] # self.ues
ues_standards = [ue for ue in self.ues if ue.type == sco_codes.UE_STANDARD]
# Les UEs en fonction des parcours
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours() self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
# Les compétences associées aux UEs # Les compétences associées aux UEs (définies par les acronymes)
self.competences = {} self.competences = {}
"""L'association acronyme d'UEs -> compétence"""
for ue in self.ues: for ue in self.ues:
if ue.type != UE_SPORT: if ue.type == sco_codes.UE_STANDARD:
assert ue.niveau_competence, ScoValueError("Des UEs ne sont pas rattachées à des compétences") assert ue.niveau_competence, ScoValueError(
"Des UEs ne sont pas rattachées à des compétences"
)
nom = ue.niveau_competence.competence.titre nom = ue.niveau_competence.competence.titre
self.competences[ue.ue_id] = nom self.competences[ue.acronyme] = nom
# Les acronymes des UEs
self.ues_to_acronymes = {ue.id: ue.acronyme for ue in ues_standards}
self.acronymes_sorted = sorted(self.ues_to_acronymes.values())
"""Les acronymes de UE triés par ordre alphabétique"""
# Les tags personnalisés et auto: # Les tags personnalisés et auto:
tags_dict = self._get_tags_dict() tags_dict = self._get_tags_dict()
self._check_tags(tags_dict) self._check_tags(tags_dict)
# self.tags = [tag for cat in dict_tags for tag in dict_tags[cat]]
# Les coefficients # Les coefficients pour le calcul de la moyenne générale
matrice_coeffs = self.ues_inscr_parcours_df * [ self.matrice_coeffs_moy_gen = self.ues_inscr_parcours_df * [
ue.ects ue.ects for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé
for ue in ues_hors_sport # if ue.type != UE_SPORT <= déjà supprimé
] ]
# Les capitalisations (mask etuids x acronyme_ue valant True si capitalisée, False sinon)
self.capitalisations = self._get_capitalisations(ues_standards)
# Calcul des moyennes & les classements de chaque étudiant à chaque tag # Calcul des moyennes & les classements de chaque étudiant à chaque tag
self.moyennes_tags = {} self.moyennes_tags = {}
@ -111,46 +118,56 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
# pe_affichage.pe_print(f" -> Traitement du tag {tag}") # pe_affichage.pe_print(f" -> Traitement du tag {tag}")
infos_tag = tags_dict["personnalises"][tag] infos_tag = tags_dict["personnalises"][tag]
moy_ues_tag = self.compute_moy_ues_tag(infos_tag) moy_ues_tag = self.compute_moy_ues_tag(infos_tag)
# moy_gen_tag = self.compute_moy_gen_tag(moy_ues_tag)
self.moyennes_tags[tag] = MoyennesTag( self.moyennes_tags[tag] = MoyennesTag(
tag, tag, moy_ues_tag, self.matrice_coeffs_moy_gen
moy_ues_tag,
matrice_coeffs
) )
# Ajoute les d'UE moyennes générales de BUT pour le semestre considéré # Ajoute les moyennes par UEs + la moyenne générale (but)
# moy_gen_but = self.nt.etud_moy_gen
# self.moyennes_tags["but"] = MoyenneTag("but", [], None, moy_gen_but, )
# Ajoute les moyennes par UEs (et donc par compétence) + la moyenne générale (but)
df_ues = pd.DataFrame( df_ues = pd.DataFrame(
{ue.id: self.etud_moy_ue[ue.id] for ue in ues_hors_sport}, {ue.id: self.etud_moy_ue[ue.id] for ue in ues_standards},
index=self.etudids, index=self.etudids,
) )
# Transforme les UEs en acronyme
colonnes = df_ues.columns
acronymes = [self.ues_to_acronymes[col] for col in colonnes]
df_ues.columns = acronymes
self.moyennes_tags["but"] = MoyennesTag( self.moyennes_tags["but"] = MoyennesTag(
"but", df_ues, matrice_coeffs # , moy_gen_but "but", df_ues, self.matrice_coeffs_moy_gen # , moy_gen_but
) )
self.tags_sorted = self.get_all_tags() self.tags_sorted = self.get_all_tags()
"""Tags (personnalisés+compétences) par ordre alphabétique""" """Tags (personnalisés+compétences) par ordre alphabétique"""
# Synthétise l'ensemble des moyennes dans un dataframe
# self.notes = self.df_notes()
# """Dataframe synthétique des notes par tag"""
# pe_affichage.pe_print(
# f" => Traitement des tags {', '.join(self.tags_sorted)}"
# )
def get_repr(self, verbose=False): def get_repr(self, verbose=False):
"""Nom affiché pour le semestre taggué""" """Nom affiché pour le semestre taggué"""
if verbose: if verbose:
return f"{self.formsemestre} ({self.formsemestre.formsemestre_id})" return f"{self.formsemestre} (#{self.formsemestre.formsemestre_id})"
else: else:
return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True) return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
def _get_capitalisations(self, ues_hors_sport) -> pd.DataFrame:
"""Renvoie un dataFrame résumant les UEs capitalisables par les
étudiants, d'après les décisions de jury
Args:
ues_hors_sport: Liste des UEs autres que le sport
"""
capitalisations = pd.DataFrame(False, index=self.etudids_sorted, columns=self.acronymes_sorted)
self.get_formsemestre_validations() # charge les validations
res_jury = self.validations
if res_jury:
for etud in self.etuds:
etudid = etud.etudid
decisions = res_jury.decisions_jury_ues.get(etudid, {})
for ue in ues_hors_sport:
if ue.id in decisions and decisions[ue.id]["code"] == sco_codes.ADM:
capitalisations.loc[etudid, ue.acronyme] = True
# pe_affichage.pe_print(
# f" ⚠ Capitalisation de {ue.acronyme} pour {etud.etat_civil}"
# )
return capitalisations
def compute_moy_ues_tag(self, info_tag: dict[int, dict]) -> pd.DataFrame: def compute_moy_ues_tag(self, info_tag: dict[int, dict]) -> pd.DataFrame:
"""Calcule la moyenne par UE des étudiants pour un tag, """Calcule la moyenne par UE des étudiants pour un tag,
en ayant connaissance des informations sur le tag. en ayant connaissance des informations sur le tag.
@ -164,7 +181,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
# Adaptation du mask de calcul des moyennes au tag visé # Adaptation du mask de calcul des moyennes au tag visé
modimpls_mask = [ modimpls_mask = [
modimpl.module.ue.type != UE_SPORT modimpl.module.ue.type == sco_codes.UE_STANDARD
for modimpl in self.formsemestre.modimpls_sorted for modimpl in self.formsemestre.modimpls_sorted
] ]
@ -216,9 +233,6 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
self.formsemestre self.formsemestre
) )
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys()))) noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
pe_affichage.pe_print(
f"* Tags personnalisés (extraits du programme de formation) : {', '.join(noms_tags_perso)}"
)
# Les tags automatiques # Les tags automatiques
# Déduit des compétences # Déduit des compétences
@ -229,8 +243,11 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
dict_tags["auto"] = {"but": {}} dict_tags["auto"] = {"but": {}}
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
# Affichage
pe_affichage.pe_print( pe_affichage.pe_print(
f"* Tags automatiquement ajoutés : {', '.join(noms_tags_auto)}" f"* Tags du programme de formation : {', '.join(noms_tags_perso)} "
+ f"Tags automatiques : {', '.join(noms_tags_auto)}"
) )
return dict_tags return dict_tags
@ -312,4 +329,3 @@ def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
} }
return synthese_tags return synthese_tags

View File

@ -36,19 +36,23 @@ Created on Fri Sep 9 09:15:05 2016
@author: barasc @author: barasc
""" """
from app.comp.res_sem import load_formsemestre_results from app.pe import pe_affichage, pe_comp
from app.models import UniteEns, FormSemestre import app.pe.pe_ressemtag as pe_ressemtag
from app.pe import pe_affichage
from app.pe.pe_ressemtag import ResSemBUTTag
import pandas as pd import pandas as pd
import numpy as np 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
class SxTag(TableTag): class SxTag(TableTag):
def __init__(self, sxtag_id: (int, int), ressembuttags: dict[int, ResSemBUTTag]): def __init__(
self,
sxtag_id: (str, int),
rcf: pe_rcf.RCF,
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'
(par ex. 'S1', 'S2', ...) avec une orientation par UE : (par ex. 'S1', 'S2', ...) avec une orientation par UE :
@ -76,10 +80,14 @@ class SxTag(TableTag):
assert sxtag_id and len(sxtag_id) == 2 and sxtag_id[1] in ressembuttags assert sxtag_id and len(sxtag_id) == 2 and sxtag_id[1] in ressembuttags
self.sxtag_id: (int, int) = sxtag_id self.sxtag_id: (str, int) = sxtag_id
"""Identifiant du SxTag de la forme (semestre_id, fid_semestre_final)""" """Identifiant du SxTag de la forme (nom_Sx, fid_semestre_final)"""
self.ressembuttags = ressembuttags self.rcf = rcf
"""Le RCF sur lequel il s'appuie"""
# Les resultats des semestres taggués à prendre en compte dans le RCF
self.ressembuttags = {fid: ressembuttags[fid] for fid in rcf.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
@ -87,9 +95,13 @@ class SxTag(TableTag):
self.ressembuttag_final = ressembuttags[self.fid_final] self.ressembuttag_final = ressembuttags[self.fid_final]
"""Le ResSemBUTTag final""" """Le ResSemBUTTag final"""
# Les étudiants (etuds, états civils & etudis)
self.etuds = ressembuttags[self.fid_final].etuds self.etuds = ressembuttags[self.fid_final].etuds
"""Les étudiants du ReSemBUTTag final"""
# Ajout les etudids et les états civils
self.add_etuds(self.etuds) self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids)
"""Les etudids triés"""
# Affichage # Affichage
pe_affichage.pe_print(f"--> {self.get_repr()}") pe_affichage.pe_print(f"--> {self.get_repr()}")
@ -103,50 +115,87 @@ class SxTag(TableTag):
moy_sem_final = self.ressembuttag_final.moyennes_tags["but"] moy_sem_final = self.ressembuttag_final.moyennes_tags["but"]
self.ues = list(moy_sem_final.matrice_notes.columns) self.ues = list(moy_sem_final.matrice_notes.columns)
# L'association UE-compétences extraites du dernier semestre
self.competences = self.ressembuttag_final.competences
# Les acronymes des UE # Les acronymes des UE
self.acronymes_ues_sorted = sorted(self.ues) self.acronymes_ues_sorted = sorted(self.ues)
# Les inscriptions des étudiants aux UEs # Les inscriptions des étudiants aux UEs
# => ne conserve que les UEs du semestre final (pour les redoublants) # => ne conserve que les UEs du semestre final (pour les redoublants)
self.matrice_coeffs = self.ressembuttag_final.moyennes_tags[ self.ues_inscr_parcours_df = self.ressembuttag_final.ues_inscr_parcours_df
"but" self.ues_inscr_parcours_df.sort_index()
].matrice_coeffs
self.ues_inscr_parcours = ~np.isnan(self.matrice_coeffs.to_numpy())
# Les coeffs pour la moyenne générale
self.matrice_coeffs_moy_gen = self.ressembuttag_final.moyennes_tags[
"but"
].matrice_coeffs_moy_gen
self.matrice_coeffs_moy_gen.sort_index() # Trie les coeff par etudids
# Les moyennes par tag # Les moyennes par tag
self.moyennes_tags: dict[str, pd.DataFrame] = {} self.moyennes_tags: dict[str, pd.DataFrame] = {}
"""Les notes aux UEs dans différents tags""" """Les notes aux UEs dans différents tags"""
# Masque des inscriptions # Masque des inscriptions et des capitalisations
inscr_mask = self.ues_inscr_parcours self.masque_df, masque_cube = compute_masques_ues_cube(
self.etudids_sorted,
self.acronymes_ues_sorted,
self.ressembuttags,
self.fid_final,
)
# Affichage pour debug
for etud in self.etuds:
cap = []
for frmsem_id in self.ressembuttags:
if frmsem_id != self.fid_final:
for accr in self.acronymes_ues_sorted:
if self.masque_df[frmsem_id].loc[etud.etudid, accr] > 0.0:
cap += [accr]
if cap:
pe_affichage.pe_print(
f" ⚠ Capitalisation de {etud.etat_civil} : {', '.join(cap)}"
)
for tag in self.tags_sorted: for tag in self.tags_sorted:
# Cube de note etudids x UEs # Cube de note etudids x UEs
notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues_sorted) notes_df, notes_cube = compute_notes_ues_cube(
tag, self.etudids_sorted, self.acronymes_ues_sorted, self.ressembuttags
)
# Calcule des moyennes sous forme d'un dataframe"""
matrice_moys_ues = compute_notes_ues(
# self.ues_inscr_parcours = ~np.isnan(self.matrice_coeffs.to_numpy())
# inscr_mask = self.ues_inscr_parcours
# Calcule des moyennes sous forme d'un dataframe
inscr_mask = ~np.isnan(self.ues_inscr_parcours_df.to_numpy())
matrice_moys_ues: pd.DataFrame = compute_notes_ues(
notes_cube, notes_cube,
self.etudids, masque_cube,
self.etudids_sorted,
self.acronymes_ues_sorted, self.acronymes_ues_sorted,
inscr_mask, inscr_mask,
) )
# Les profils d'ects (pour debug) # Les profils d'ects (pour debug)
profils_ects = [] profils_ects = []
for i in self.matrice_coeffs.index: for i in self.matrice_coeffs_moy_gen.index:
val = tuple(self.matrice_coeffs.loc[i].fillna("x")) val = tuple(self.matrice_coeffs_moy_gen.loc[i].fillna("x"))
if tuple(val) not in profils_ects: if tuple(val) not in profils_ects:
profils_ects.append(tuple(val)) profils_ects.append(tuple(val))
# Les moyennes # Les moyennes
self.moyennes_tags[tag] = MoyennesTag(tag, self.moyennes_tags[tag] = MoyennesTag(
matrice_moys_ues, tag, matrice_moys_ues, self.matrice_coeffs_moy_gen
self.matrice_coeffs) )
pe_affichage.pe_print(f"> MoyTag pour {tag} avec") pe_affichage.pe_print(
pe_affichage.pe_print(f" - ues={self.acronymes_ues_sorted}") f"> MoyTag 🏷{tag} avec "
pe_affichage.pe_print(f" - ects={profils_ects}") + f"ues={self.acronymes_ues_sorted} "
+ f"ects={profils_ects}"
)
def __eq__(self, other): def __eq__(self, other):
"""Egalité de 2 SxTag sur la base de leur identifiant""" """Egalité de 2 SxTag sur la base de leur identifiant"""
@ -155,41 +204,49 @@ class SxTag(TableTag):
def get_repr(self, verbose=False) -> str: def get_repr(self, verbose=False) -> str:
"""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)"""
affichage = [str(fid) for fid in self.ressembuttags] if verbose:
return f"{self.sxtag_id[0]}Tag ({'+'.join(affichage)})" return f"{self.sxtag_id[0]}Tag basé sur {self.rcf.get_repr()}"
else:
# affichage = [str(fid) for fid in self.ressembuttags]
return f"{self.sxtag_id[0]}Tag (#{self.fid_final})"
def compute_notes_ues_cube(self, tag, acronymes_ues_sorted):
def compute_notes_ues_cube(
tag, etudids_sorted, acronymes_ues_sorted, ressembuttags
) -> (pd.DataFrame, np.array):
"""Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé) """Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé)
nécessaire au calcul des moyennes du tag pour le RCS Sx nécessaire au calcul des moyennes du tag pour le RCS Sx.
(Renvoie également le dataframe associé pour debug).
Args:
etudids_sorted: La liste des etudids triés par ordre croissant (dim 0)
acronymes_ues_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1)
ressembuttags: Le dictionnaire des résultats de semestres BUT (tous tags confondus)
""" """
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2) # Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
etudids = [etud.etudid for etud in self.etuds] # etudids_sorted = etudids_sorted
# acronymes_ues = sorted([ue.acronyme for ue in self.ues.values()]) # acronymes_ues = sorted([ue.acronyme for ue in selMf.ues.values()])
semestres_id = list(self.ressembuttags.keys()) semestres_id = list(ressembuttags.keys())
dfs = {} dfs = {}
for frmsem_id in semestres_id: for frmsem_id in semestres_id:
# Partant d'un dataframe vierge # Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids, columns=acronymes_ues_sorted) df = pd.DataFrame(np.nan, index=etudids_sorted, columns=acronymes_ues_sorted)
# Charge les notes du semestre tag # Charge les notes du semestre tag
sem_tag = self.ressembuttags[frmsem_id] sem_tag = ressembuttags[frmsem_id]
moys_tag = sem_tag.moyennes_tags[tag] moys_tag = sem_tag.moyennes_tags[tag]
notes = moys_tag.matrice_notes # dataframe etudids x ues notes = moys_tag.matrice_notes # dataframe etudids x ues
acronymes_ues_sem = list(
notes.columns
) # les acronymes des UEs du semestre tag
# UEs communes à celles du SemTag (celles du dernier semestre du RCS) # les étudiants et les acronymes communs
ues_communes = list(set(acronymes_ues_sorted) & set(acronymes_ues_sem)) etudids_communs, acronymes_communs = pe_comp.find_index_and_columns_communs(
df, notes
# Etudiants communs )
etudids_communs = df.index.intersection(notes.index)
# Recopie # Recopie
df.loc[etudids_communs, ues_communes] = notes.loc[ df.loc[etudids_communs, acronymes_communs] = notes.loc[
etudids_communs, ues_communes etudids_communs, acronymes_communs
] ]
# Supprime tout ce qui n'est pas numérique # Supprime tout ce qui n'est pas numérique
@ -202,13 +259,68 @@ class SxTag(TableTag):
"""Réunit les notes sous forme d'un cube etudids x ues x semestres""" """Réunit les notes sous forme d'un cube etudids x ues x semestres"""
semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs] semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs]
etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1) etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1)
return etudids_x_ues_x_semestres return dfs, etudids_x_ues_x_semestres
def compute_masques_ues_cube(
etudids_sorted: list[int],
acronymes_ues_sorted: list[str],
ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag],
formsemestre_id_final: int,
) -> (pd.DataFrame, np.array):
"""Construit le cube traduisant le masque des UEs à prendre en compte dans le calcul
des moyennes, en utilisant le df capitalisations de chaque ResSemBUTTag
Ce masque contient : 1 si la note doit être prise en compte ; 0 sinon
Args:
etudids_sorted: La liste des etudids triés par ordre croissant (dim 0)
acronymes_ues_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1)
# ues_inscr_parcours_df: Le dataFrame des inscriptions au UE en fonction du parcours
ressembuttags: Le dictionnaire des résultats de semestres BUT (tous tags confondus)
formsemestre_id_final: L'identifiant du formsemestre_id_final (dont il faut forcément prendre en compte les coeffs)
"""
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
# etudids_sorted = etudids_sorted
# acronymes_ues = sorted([ue.acronyme for ue in selMf.ues.values()])
semestres_id = list(ressembuttags.keys())
dfs = {}
for frmsem_id in semestres_id:
# Partant d'un dataframe contenant des 1.0
if frmsem_id == formsemestre_id_final:
df = pd.DataFrame(1.0, index=etudids_sorted, columns=acronymes_ues_sorted)
else: # semestres redoublés
df = pd.DataFrame(0.0, index=etudids_sorted, columns=acronymes_ues_sorted)
# Traitement des capitalisations
capitalisations = ressembuttags[frmsem_id].capitalisations
capitalisations = capitalisations.replace(True, 1.0).replace(False, 0.0)
# Met à 0 les coeffs des UEs non capitalisées : 1.0*False => 0.0
etudids_communs, acronymes_communs = pe_comp.find_index_and_columns_communs(
df, capitalisations
)
df.loc[etudids_communs, acronymes_communs] = capitalisations.loc[
etudids_communs, acronymes_communs
]
# Stocke le df
dfs[frmsem_id] = df
"""Réunit les notes sous forme d'un cube etudids x ues x semestres"""
semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs]
etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1)
return dfs, etudids_x_ues_x_semestres
def compute_notes_ues( def compute_notes_ues(
set_cube: np.array, set_cube: np.array,
etudids: list, masque_cube: np.array,
acronymes_ues: list, etudids_sorted: list,
acronymes_ues_sorted: list,
inscr_mask: np.array, inscr_mask: np.array,
): ):
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE """Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
@ -217,8 +329,10 @@ def compute_notes_ues(
Args: Args:
set_cube: notes moyennes aux modules ndarray set_cube: notes moyennes aux modules ndarray
(semestre_ids x etudids x UEs), des floats avec des NaN (semestre_ids x etudids x UEs), des floats avec des NaN
etudids: liste des étudiants (dim. 0 du cube) masque_cube: masque indiquant si la note doit être prise en compte ndarray
acronymes_ues: liste des acronymes des ues (dim. 1 du cube) (semestre_ids x etudids x UEs), des 1.0 ou des 0.0
etudids_sorted: liste des étudiants (dim. 0 du cube) trié par etudid
acronymes_ues_sorted: liste des acronymes des ues (dim. 1 du cube) trié par acronyme
inscr_mask: masque etudids x UE traduisant les inscriptions des inscr_mask: masque etudids x UE traduisant les inscriptions des
étudiants aux UE (du semestre terminal) étudiants aux UE (du semestre terminal)
Returns: Returns:
@ -227,18 +341,21 @@ def compute_notes_ues(
""" """
nb_etuds, nb_ues, nb_semestres = set_cube.shape nb_etuds, nb_ues, nb_semestres = set_cube.shape
nb_etuds_mask, nb_ues_mask = inscr_mask.shape nb_etuds_mask, nb_ues_mask = inscr_mask.shape
assert nb_etuds == len(etudids) assert nb_etuds == len(etudids_sorted)
assert nb_ues == len(acronymes_ues) assert nb_ues == len(acronymes_ues_sorted)
assert nb_etuds == nb_etuds_mask assert nb_etuds == nb_etuds_mask
assert nb_ues == nb_ues_mask assert nb_ues == nb_ues_mask
# Quelles entrées du cube contiennent des notes ? # Entrées à garder dans le cube en fonction du masque d'inscription aux UEs du parcours
mask = ~np.isnan(set_cube)
# Entrées à garder dans le cube en fonction du mask d'inscription
inscr_mask_3D = np.stack([inscr_mask] * nb_semestres, axis=-1) inscr_mask_3D = np.stack([inscr_mask] * nb_semestres, axis=-1)
set_cube = set_cube * inscr_mask_3D set_cube = set_cube * inscr_mask_3D
# Entrées à garder en fonction des UEs capitalisées ou non
set_cube = set_cube * masque_cube
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
# Enlève les NaN du cube pour les entrées manquantes : NaN -> -1.0 # Enlève les NaN du cube pour les entrées manquantes : NaN -> -1.0
set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0) set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0)
@ -252,8 +369,8 @@ def compute_notes_ues(
# Le dataFrame # Le dataFrame
etud_moy_tag_df = pd.DataFrame( etud_moy_tag_df = pd.DataFrame(
etud_moy, etud_moy,
index=etudids, # les etudids index=etudids_sorted, # les etudids
columns=acronymes_ues, # les tags columns=acronymes_ues_sorted, # les tags
) )
etud_moy_tag_df.fillna(np.nan) etud_moy_tag_df.fillna(np.nan)

View File

@ -52,9 +52,7 @@ class TableTag(object):
# Les étudiants # Les étudiants
# self.etuds: list[Identite] = None # A venir # self.etuds: list[Identite] = None # A venir
"""Les étudiants""" """Les étudiants"""
# self.etats_civils: dict[int, Identite] = None # self.etudids: list[int] = {}
"""Les états civils"""
# self.etudids: list[int] = None
"""Les etudids""" """Les etudids"""
def add_etuds(self, etuds: list[Identite]): def add_etuds(self, etuds: list[Identite]):
@ -64,8 +62,7 @@ class TableTag(object):
etuds: la liste des identités de l'étudiant etuds: la liste des identités de l'étudiant
""" """
# self.etuds = etuds # self.etuds = etuds
self.etats_civils = {etud.etudid: etud.etat_civil for etud in self.etuds} self.etudids = list({etud.etudid for etud in etuds})
self.etudids = list(self.etats_civils.keys())
def get_all_tags(self): def get_all_tags(self):
"""Liste des tags de la table, triée par ordre alphabétique, """Liste des tags de la table, triée par ordre alphabétique,
@ -88,8 +85,8 @@ class TableTag(object):
Le dataframe des notes et des classements Le dataframe des notes et des classements
""" """
etudiants = self.etats_civils etudiants = {etud.etudid: [etud.nom, etud.prenom] for etud in self.etuds}
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"]) df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom", "prenom"])
tags_tries = self.get_all_tags() tags_tries = self.get_all_tags()
for tag in tags_tries: for tag in tags_tries:
@ -104,7 +101,7 @@ class TableTag(object):
df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag} (gen)")) df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag} (gen)"))
df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag} (gen)")) df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag} (gen)"))
df.sort_values(by=['nom']) df.sort_values(by=["nom", "prenom"])
return df return df

0
app/pe/rcss/__init__.py Normal file
View File

57
app/pe/rcss/pe_rcf.py Normal file
View File

@ -0,0 +1,57 @@
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

69
app/pe/rcss/pe_rcrcf.py Normal file
View File

@ -0,0 +1,69 @@
##############################################################################
# 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

62
app/pe/rcss/pe_rcs.py Normal file
View File

@ -0,0 +1,62 @@
##############################################################################
# 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
import app.pe.rcss.rcss_constantes as rcss_constantes
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"]
class RCS:
"""Modélise un regroupement cohérent de semestres (formsemestre ou de Sx)"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
self.nom: str = nom_rcs
"""Nom du RCS"""
self.formsemestre_final: FormSemestre = semestre_final
"""FormSemestre terminal du RCS"""
self.rang_final = self.formsemestre_final.semestre_id
"""Le 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)"""
def get_formsemestre_id_final(self) -> int:
"""Renvoie l'identifiant du formsemestre final du RCS
Returns:
L'id du formsemestre final (marquant la fin) du RCS
"""
return self.formsemestre_final.formsemestre_id
def __str__(self):
"""Représentation textuelle d'un RCS"""
return f"{self.nom}[#{self.formsemestre_final.formsemestre_id}{self.formsemestre_final.date_fin.year}]"
def get_repr(self, verbose=True):
"""Représentation textuelle d'un RCS"""
return self.__str__()
def __eq__(self, other):
"""Egalité de RCS"""
return (
self.nom == other.nom
and self.formsemestre_final == other.formsemestre_final
)

View File

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