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 unicodedata
import pandas as pd
from flask import g
import app.scodoc.sco_utils as scu
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.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]}"""
cosemestres_tries = {}
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
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_moytag import MoyennesTag
from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_rcs import RCS, RCSsJuryPE
from app.pe.pe_rcstag import RCSTag
import app.pe.pe_etudiant as pe_etudiant
import app.pe.rcss.pe_rcs as pe_rcs
import app.pe.pe_rcss_jury as pe_rcss_jury
import app.pe.pe_rcstag as pe_rcstag
class RCSInterclasseTag(TableTag):
@ -58,9 +59,9 @@ class RCSInterclasseTag(TableTag):
def __init__(
self,
nom_rcs: str,
etudiants: EtudiantsJuryPE,
rcss_jury_pe: RCSsJuryPE,
rcss_tags: dict[tuple, RCSTag],
etudiants: pe_etudiant.EtudiantsJuryPE,
rcss_jury_pe: pe_rcss_jury.RCSsJuryPE,
rcss_tags: dict[tuple, pe_rcstag.RCSTag],
):
TableTag.__init__(self)
@ -80,21 +81,21 @@ class RCSInterclasseTag(TableTag):
# Les trajectoires (et leur version tagguées), en ne gardant que
# 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"""
for trajectoire_id in rcss_jury_pe.rcss:
trajectoire = rcss_jury_pe.rcss[trajectoire_id]
if trajectoire_id[0] == nom_rcs:
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"""
for trajectoire_id in self.rcss:
self.trajectoires_taggues[trajectoire_id] = rcss_tags[trajectoire_id]
# Les trajectoires suivies par les étudiants du jury, en ne gardant que
# 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
compte pour l'aggrégat"""
for etudid in self.diplomes_ids:

View File

@ -50,15 +50,16 @@ from zipfile import ZipFile
import numpy as np
import pandas as pd
import app.pe.rcss.rcss_constantes as rcss_constants
from app.pe import pe_sxtag
from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE
import app.pe.pe_affichage as pe_affichage
import app.pe.pe_etudiant as pe_etudiant
import app.pe.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_ressemtag import ResSemBUTTag
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):
@ -94,7 +95,7 @@ class JuryPE(object):
self.etudiants.find_etudiants()
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"""
self.zipdata = io.BytesIO()
@ -102,8 +103,8 @@ class JuryPE(object):
if not self.diplomes_ids:
pe_affichage.pe_print("*** Aucun étudiant diplômé")
else:
try:
self._gen_xls_diplomes(zipfile)
self._gen_xls_ressembuttags(zipfile)
self._gen_rcss()
self._gen_xls_sxtags(zipfile)
@ -112,6 +113,8 @@ class JuryPE(object):
# self._gen_xls_interclassements_rcss(zipfile)
# self._gen_xls_synthese_jury_par_tag(zipfile)
# self._gen_xls_synthese_par_etudiant(zipfile)
except Exception as e:
raise e
# et le log
self._add_log_to_zip(zipfile)
@ -150,7 +153,7 @@ class JuryPE(object):
formsemestres = get_formsemestres_etudiants(self.etudiants)
pe_affichage.pe_print(
f" --> {len(formsemestres)} résultats de semestres à considérer"
f"--> {len(formsemestres)} résultats de semestres à considérer"
)
self.ressembuttags = {}
@ -186,14 +189,15 @@ class JuryPE(object):
)
self.rcss_jury.cree_rcss(self.etudiants)
self.rcss_jury.cree_rcfs(self.etudiants)
def _gen_xls_sxtags(self, zipfile: ZipFile):
"""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).
"""
# 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)
# 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():
# SxTag traduisant le RCF
sxtag_id = rcf_id
# Les resultats des semestres taggués à prendre en compte dans le RCF
ressemstags = {
fid: self.ressembuttags[fid] for fid in rcf.semestres_aggreges
}
self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, ressemstags)
self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, rcf, self.ressembuttags)
# Intègre le bilan des semestres taggués au zip final
output = io.BytesIO()
@ -232,10 +233,11 @@ class JuryPE(object):
def _gen_rcrcfs(self):
"""Génère les regroupements cohérents de RCFs qu'ont suivi chaque étudiant"""
pe_affichage.pe_print("*** Génère les RCRCF (regroupements de RCF de type Sx) amenant du S1 à un semestre final***")
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)
def _gen_xls_rcrcss_tags(self, zipfile: ZipFile):
"""Génère les RCS taggués traduisant les moyennes (orientées compétences)
de regroupements de semestre de type Sx, xA ou xS.
@ -268,7 +270,7 @@ class JuryPE(object):
output, engine="openpyxl"
) as writer:
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()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
@ -276,7 +278,7 @@ class JuryPE(object):
self.add_file_to_zip(
zipfile,
f"RCS_taggues_{self.diplome}.xlsx",
f"RCRCFs_{self.diplome}.xlsx",
output.read(),
path="details",
)
@ -431,7 +433,7 @@ class JuryPE(object):
# Ajout des aggrégats
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
# considéré
@ -632,7 +634,7 @@ def get_formsemestres_etudiants(etudiants: pe_etudiant.EtudiantsJuryPE) -> dict:
def compute_interclassements(
etudiants: pe_etudiant.EtudiantsJuryPE,
trajectoires_jury_pe: pe_rcs.RCSsJuryPE,
trajectoires_jury_pe: pe_rcss_jury.RCSsJuryPE,
trajectoires_tagguees: dict[tuple, pe_rcs.RCS],
):
"""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
"""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
(toutes UE ou compétences confondues). NaN si étudiant non inscrit"""
@ -193,7 +193,7 @@ class MoyennesTag:
self.moyennes[col] = Moyenne(notes)
# 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
"""Les notes générales (moyenne toutes UEs confonudes)"""
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.rcss.rcss_constantes as 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
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
from app.pe import pe_affichage
class RCSsJuryPE:
@ -257,28 +20,28 @@ class RCSsJuryPE:
self.annee_diplome = annee_diplome
"""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"""
self.rcss_suivis: dict[int:dict] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de 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}"""
self.rcfs_suivis: dict[int:dict] = {}
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
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}"""
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: EtudiantsJuryPE):
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
@ -286,17 +49,17 @@ class RCSsJuryPE:
etudiants: Les étudiants à prendre en compte dans le Jury PE
"""
tous_les_aggregats = rcss_constantes.TOUS_LES_SEMESTRES + rcss_constantes.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
for etudid in etudiants.trajectoires:
self.rcss_suivis[etudid] = {
aggregat: None
for aggregat in pe_comp.TOUS_LES_SEMESTRES
+ TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
for aggregat in tous_les_aggregats
}
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
# 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]
for etudid in etudiants.trajectoires:
@ -305,12 +68,12 @@ class RCSsJuryPE:
# (par ex: son dernier S3 en date)
trajectoire = etudiants.trajectoires[etudid][nom_semestre_terminal]
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é
rcs_id = (nom_rcs, formsemestre_final.formsemestre_id)
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]
# La liste des semestres de l'étudiant à prendre en compte
@ -328,23 +91,22 @@ class RCSsJuryPE:
# Affichage pour debug
jeunes = list(enumerate(self.rcss_suivis))
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():
if rcs:
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
de type Sx"""
self.rcfs = {}
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
print(self.rcfs)
for etudid in self.rcss_suivis:
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:
self.rcfs_suivis[etudid] = {}
self.rcfs_suivis[etudid][nom_rcs] = rcs
@ -355,11 +117,11 @@ class RCSsJuryPE:
pe_affichage.pe_print(f"-> {etudiants.identites[etudid].nomprenom} :")
for nom_rcs, rcs in self.rcfs_suivis[etudid].items():
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:
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
analysés (trajectoires traduisant son parcours dans les
différents semestres) + les mémorise dans les données de l'étudiant
@ -390,7 +152,7 @@ class RCSsJuryPE:
# Ajout du RCRCF
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]
# Ajout des RCFs au RCRCF
@ -410,6 +172,8 @@ class RCSsJuryPE:
else:
pe_affichage.pe_print(f" > RCRCF {nom_rcs}: <vide> !!! ")
def get_rcs_etudiant(
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
) -> dict[int, FormSemestre]:
@ -439,7 +203,7 @@ def get_rcs_etudiant(
numero_semestres_possibles = [numero_semestre_terminal]
elif nom_rcs.endswith("A"): # les années
numero_semestres_possibles = [
int(sem[-1]) for sem in 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
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
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.pe import pe_affichage
from app.pe.pe_ressemtag import ResSemBUTTag
import pandas as pd
import numpy as np
from app.pe.pe_rcs import RCS, RCRCF
from app.pe.pe_sxtag import SxTag
import app.pe.rcss.pe_rcs as pe_rcs
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_moytag import MoyennesTag
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)
d'un regroupement de SxTag
(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
"""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é"""
self.nom = self.get_repr()
@ -74,6 +74,8 @@ class RCSTag(TableTag):
self.formsemestre_terminal = rcrcf.formsemestre_final
"""Le formsemestre terminal"""
pe_affichage.pe_print(f"-> {self.get_repr(verbose=True)}")
# Les résultats du formsemestre terminal
nt = load_formsemestre_results(self.formsemestre_terminal)
@ -95,10 +97,12 @@ class RCSTag(TableTag):
# Les compétences (extraites de tous les Sxtags)
self.competences_sorted = self.do_complist()
"""Compétences extraites de tous les SxTag aggrégés"""
pe_affichage.pe_print(f"* Compétences : {', '.join(self.competences_sorted)}")
# Les tags
self.tags_sorted = self.do_taglist()
"""Tags extraits de tous les SxTag aggrégés"""
pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}")
# Les moyennes
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"""
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
est basée)"""
if verbose:
return self.rcrcf.get_repr(verbose=verbose)
else:
return f"{self.__class__.__name__} ({self.rcs_id})"
def compute_notes_comps_cube(self, tag):
"""Pour un tag donné, construit :
@ -154,10 +161,11 @@ class RCSTag(TableTag):
# Charge les notes et les coeffs du semestre tag
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
association_ues_comp = moys_tag.competences
association_ues_comp = self.mapping_ue_competences()
print(association_ues_comp)
ues_columns_df = notes.columns
comp_associes_aux_ues = [association_ues_comp[ue] for ue in ues_columns_df]
notes.columns = comp_associes_aux_ues
@ -204,16 +212,20 @@ class RCSTag(TableTag):
tags = []
for frmsem_id in self.sxstags:
tags.extend(self.sxstags[frmsem_id].tags_sorted)
pe_affichage.pe_print(f"* Tags : {', '.join(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):
"""Synthétise les compétences à partir des Sxtags aggrégés"""
competences = []
for sxtag_id, sxtag in self.sxstags.items():
comp = list(sxtag.moyennes_tags["but"].matrice_notes.columns)
competences.extend(comp)
return sorted(set(competences))
dict_comptences = self.mapping_ue_competences()
return sorted(set(dict_comptences.values()))
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
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
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
from app.pe.pe_moytag import MoyennesTag
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):
@ -66,44 +66,51 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
ResultatsSemestreBUT.__init__(self, formsemestre)
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é
self.nom = self.get_repr(verbose=True)
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é
self.add_etuds(self.etuds)
self.etudids_sorted = sorted(self.etudids)
# 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()
# Les compétences associées aux UEs
# Les compétences associées aux UEs (définies par les acronymes)
self.competences = {}
"""L'association acronyme d'UEs -> compétence"""
for ue in self.ues:
if ue.type != UE_SPORT:
assert ue.niveau_competence, ScoValueError("Des UEs ne sont pas rattachées à des compétences")
if ue.type == sco_codes.UE_STANDARD:
assert ue.niveau_competence, ScoValueError(
"Des UEs ne sont pas rattachées à des compétences"
)
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:
tags_dict = self._get_tags_dict()
self._check_tags(tags_dict)
# self.tags = [tag for cat in dict_tags for tag in dict_tags[cat]]
# Les coefficients
matrice_coeffs = self.ues_inscr_parcours_df * [
ue.ects
for ue in ues_hors_sport # if ue.type != UE_SPORT <= déjà supprimé
# Les coefficients pour le calcul de la moyenne générale
self.matrice_coeffs_moy_gen = self.ues_inscr_parcours_df * [
ue.ects for ue in ues_standards # 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
self.moyennes_tags = {}
@ -111,46 +118,56 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
# pe_affichage.pe_print(f" -> Traitement du tag {tag}")
infos_tag = tags_dict["personnalises"][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(
tag,
moy_ues_tag,
matrice_coeffs
tag, moy_ues_tag, self.matrice_coeffs_moy_gen
)
# Ajoute les d'UE moyennes générales de BUT pour le semestre considéré
# 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)
# Ajoute les moyennes par UEs + la moyenne générale (but)
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,
)
# 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(
"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()
"""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):
"""Nom affiché pour le semestre taggué"""
if verbose:
return f"{self.formsemestre} ({self.formsemestre.formsemestre_id})"
return f"{self.formsemestre} (#{self.formsemestre.formsemestre_id})"
else:
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:
"""Calcule la moyenne par UE des étudiants pour un 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é
modimpls_mask = [
modimpl.module.ue.type != UE_SPORT
modimpl.module.ue.type == sco_codes.UE_STANDARD
for modimpl in self.formsemestre.modimpls_sorted
]
@ -216,9 +233,6 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
self.formsemestre
)
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
# Déduit des compétences
@ -229,8 +243,11 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
dict_tags["auto"] = {"but": {}}
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
# Affichage
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
@ -312,4 +329,3 @@ def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
}
return synthese_tags

View File

@ -36,19 +36,23 @@ Created on Fri Sep 9 09:15:05 2016
@author: barasc
"""
from app.comp.res_sem import load_formsemestre_results
from app.models import UniteEns, FormSemestre
from app.pe import pe_affichage
from app.pe.pe_ressemtag import ResSemBUTTag
from app.pe import pe_affichage, pe_comp
import app.pe.pe_ressemtag as pe_ressemtag
import pandas as pd
import numpy as np
from app.pe.pe_tabletags import TableTag
from app.pe.pe_moytag import MoyennesTag
import app.pe.rcss.pe_rcf as pe_rcf
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'
(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
self.sxtag_id: (int, int) = sxtag_id
"""Identifiant du SxTag de la forme (semestre_id, fid_semestre_final)"""
self.sxtag_id: (str, int) = sxtag_id
"""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 données du semestre final
@ -87,9 +95,13 @@ class SxTag(TableTag):
self.ressembuttag_final = ressembuttags[self.fid_final]
"""Le ResSemBUTTag final"""
# Les étudiants (etuds, états civils & etudis)
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.etudids_sorted = sorted(self.etudids)
"""Les etudids triés"""
# Affichage
pe_affichage.pe_print(f"--> {self.get_repr()}")
@ -103,50 +115,87 @@ class SxTag(TableTag):
moy_sem_final = self.ressembuttag_final.moyennes_tags["but"]
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
self.acronymes_ues_sorted = sorted(self.ues)
# Les inscriptions des étudiants aux UEs
# => ne conserve que les UEs du semestre final (pour les redoublants)
self.matrice_coeffs = self.ressembuttag_final.moyennes_tags[
"but"
].matrice_coeffs
self.ues_inscr_parcours = ~np.isnan(self.matrice_coeffs.to_numpy())
self.ues_inscr_parcours_df = self.ressembuttag_final.ues_inscr_parcours_df
self.ues_inscr_parcours_df.sort_index()
# 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
self.moyennes_tags: dict[str, pd.DataFrame] = {}
"""Les notes aux UEs dans différents tags"""
# Masque des inscriptions
inscr_mask = self.ues_inscr_parcours
# Masque des inscriptions et des capitalisations
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:
# 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,
self.etudids,
masque_cube,
self.etudids_sorted,
self.acronymes_ues_sorted,
inscr_mask,
)
# Les profils d'ects (pour debug)
profils_ects = []
for i in self.matrice_coeffs.index:
val = tuple(self.matrice_coeffs.loc[i].fillna("x"))
for i in self.matrice_coeffs_moy_gen.index:
val = tuple(self.matrice_coeffs_moy_gen.loc[i].fillna("x"))
if tuple(val) not in profils_ects:
profils_ects.append(tuple(val))
# Les moyennes
self.moyennes_tags[tag] = MoyennesTag(tag,
matrice_moys_ues,
self.matrice_coeffs)
self.moyennes_tags[tag] = MoyennesTag(
tag, matrice_moys_ues, self.matrice_coeffs_moy_gen
)
pe_affichage.pe_print(f"> MoyTag pour {tag} avec")
pe_affichage.pe_print(f" - ues={self.acronymes_ues_sorted}")
pe_affichage.pe_print(f" - ects={profils_ects}")
pe_affichage.pe_print(
f"> MoyTag 🏷{tag} avec "
+ f"ues={self.acronymes_ues_sorted} "
+ f"ects={profils_ects}"
)
def __eq__(self, other):
"""Egalité de 2 SxTag sur la base de leur identifiant"""
@ -155,41 +204,49 @@ class SxTag(TableTag):
def get_repr(self, verbose=False) -> str:
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
est basée)"""
affichage = [str(fid) for fid in self.ressembuttags]
return f"{self.sxtag_id[0]}Tag ({'+'.join(affichage)})"
if verbose:
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é)
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)
etudids = [etud.etudid for etud in self.etuds]
# acronymes_ues = sorted([ue.acronyme for ue in self.ues.values()])
semestres_id = list(self.ressembuttags.keys())
# 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 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
sem_tag = self.ressembuttags[frmsem_id]
sem_tag = ressembuttags[frmsem_id]
moys_tag = sem_tag.moyennes_tags[tag]
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)
ues_communes = list(set(acronymes_ues_sorted) & set(acronymes_ues_sem))
# Etudiants communs
etudids_communs = df.index.intersection(notes.index)
# les étudiants et les acronymes communs
etudids_communs, acronymes_communs = pe_comp.find_index_and_columns_communs(
df, notes
)
# Recopie
df.loc[etudids_communs, ues_communes] = notes.loc[
etudids_communs, ues_communes
df.loc[etudids_communs, acronymes_communs] = notes.loc[
etudids_communs, acronymes_communs
]
# 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"""
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 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(
set_cube: np.array,
etudids: list,
acronymes_ues: list,
masque_cube: np.array,
etudids_sorted: list,
acronymes_ues_sorted: list,
inscr_mask: np.array,
):
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
@ -217,8 +329,10 @@ def compute_notes_ues(
Args:
set_cube: notes moyennes aux modules ndarray
(semestre_ids x etudids x UEs), des floats avec des NaN
etudids: liste des étudiants (dim. 0 du cube)
acronymes_ues: liste des acronymes des ues (dim. 1 du cube)
masque_cube: masque indiquant si la note doit être prise en compte ndarray
(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
étudiants aux UE (du semestre terminal)
Returns:
@ -227,18 +341,21 @@ def compute_notes_ues(
"""
nb_etuds, nb_ues, nb_semestres = set_cube.shape
nb_etuds_mask, nb_ues_mask = inscr_mask.shape
assert nb_etuds == len(etudids)
assert nb_ues == len(acronymes_ues)
assert nb_etuds == len(etudids_sorted)
assert nb_ues == len(acronymes_ues_sorted)
assert nb_etuds == nb_etuds_mask
assert nb_ues == nb_ues_mask
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
# Entrées à garder dans le cube en fonction du mask d'inscription
# Entrées à garder dans le cube en fonction du masque d'inscription aux UEs du parcours
inscr_mask_3D = np.stack([inscr_mask] * nb_semestres, axis=-1)
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
set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0)
@ -252,8 +369,8 @@ def compute_notes_ues(
# Le dataFrame
etud_moy_tag_df = pd.DataFrame(
etud_moy,
index=etudids, # les etudids
columns=acronymes_ues, # les tags
index=etudids_sorted, # les etudids
columns=acronymes_ues_sorted, # les tags
)
etud_moy_tag_df.fillna(np.nan)

View File

@ -52,9 +52,7 @@ class TableTag(object):
# Les étudiants
# self.etuds: list[Identite] = None # A venir
"""Les étudiants"""
# self.etats_civils: dict[int, Identite] = None
"""Les états civils"""
# self.etudids: list[int] = None
# self.etudids: list[int] = {}
"""Les etudids"""
def add_etuds(self, etuds: list[Identite]):
@ -64,8 +62,7 @@ class TableTag(object):
etuds: la liste des identités de l'étudiant
"""
# self.etuds = etuds
self.etats_civils = {etud.etudid: etud.etat_civil for etud in self.etuds}
self.etudids = list(self.etats_civils.keys())
self.etudids = list({etud.etudid for etud in etuds})
def get_all_tags(self):
"""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
"""
etudiants = self.etats_civils
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
etudiants = {etud.etudid: [etud.nom, etud.prenom] for etud in self.etuds}
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom", "prenom"])
tags_tries = self.get_all_tags()
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["classements"].rename(f"Class {tag} (gen)"))
df.sort_values(by=['nom'])
df.sort_values(by=["nom", "prenom"])
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")]