Etat intermédiaire sur les interclassements et la synthèse du jury (données sans notes ou tags à revoir)

This commit is contained in:
Cléo Baras 2024-02-21 20:02:38 +01:00
parent 40a57a9b86
commit 746314b2fb
12 changed files with 897 additions and 614 deletions

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

View File

@ -0,0 +1,325 @@
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
"""
Created on Thu Sep 8 09:36:33 2016
@author: barasc
"""
import pandas as pd
import numpy as np
from app.models import Identite
from app.pe.moys import pe_tabletags, pe_moy, pe_moytag, pe_sxtag
from app.pe.rcss import pe_rcs
import app.pe.pe_comp as pe_comp
class InterClassTag(pe_tabletags.TableTag):
"""
Interclasse l'ensemble des étudiants diplômés à une année
donnée (celle du jury), pour un RCS donné (par ex: 'S2', '3S'), qu'il soit
de type SemX ou RCSemX,
en reportant les moyennes obtenues sur à la version tagguée
du RCS (de type SxTag ou RCSTag).
Sont ensuite calculés les classements (uniquement)
sur les étudiants diplômes.
Args:
nom_rcs: Le nom de l'aggrégat
type_interclassement: Le type d'interclassement (par UE ou par compétences)
etudiants_diplomes: L'identité des étudiants diplômés
rcss: Un dictionnaire {(nom_rcs, fid_final): RCS} donnant soit
les SemX soit les RCSemX recencés par le jury PE
rcstag: Un dictionnaire {(nom_rcs, fid_final): RCSTag} donnant
soit les SxTag (associés aux SemX)
soit les RCSTags (associés au RCSemX) calculés par le jury PE
suivis: Un dictionnaire associé à chaque étudiant son rcss
(de la forme ``{etudid: {nom_rcs: RCS_suivi}}``)
"""
def __init__(
self,
nom_rcs: str,
type_interclassement: str,
etudiants_diplomes: dict[int, Identite],
rcss: dict[(str, int) : pe_rcs.RCS],
rcstags: dict[(str, int) : pe_tabletags.TableTag],
suivis: dict[int:dict],
):
pe_tabletags.TableTag.__init__(self)
self.nom_rcs: str = nom_rcs
"""Le nom du RCS interclassé"""
# Le type d'interclassement
self.type = type_interclassement
# Les informations sur les étudiants diplômés
self.etuds: list[Identite] = list(etudiants_diplomes.values())
"""Identités des étudiants diplômés"""
self.add_etuds(self.etuds)
self.diplomes_ids = set(etudiants_diplomes.keys())
"""Etudids des étudiants diplômés"""
# Les RCS de l'aggrégat (SemX ou RCSemX)
self.rcss: dict[(str, int), pe_rcs.RCS] = {}
"""Ensemble des SemX ou des RCSemX associés à l'aggrégat"""
for (nom, fid), rcs in rcss.items():
if nom == nom_rcs:
self.rcss[(nom, fid)] = rcss
# Les données tagguées
self.rcstags: dict[(str, int), pe_tabletags.TableTag] = {}
"""Ensemble des SxTag ou des RCSTags associés à l'aggrégat"""
for rcs_id in self.rcss:
self.rcstags[rcs_id] = rcstags[rcs_id]
# Les RCS (SemX ou RCSemX) suivis par les étudiants du jury,
# en ne gardant que ceux associés aux diplomés
self.suivis: dict[int, pe_rcs.RCS] = {}
"""Association entre chaque étudiant et le SxTag ou RCSTag à prendre
pour l'aggrégat"""
for etudid in self.diplomes_ids:
self.suivis[etudid] = suivis[etudid][nom_rcs]
# Les données sur les tags
self.tags_sorted = self._do_taglist()
"""Liste des tags (triés par ordre alphabétique)"""
# Les données sur les UEs (si SxTag) ou compétences (si RCSTag)
self.champs_sorted = self._do_ues_ou_competences_list()
# Construit la matrice de notes
etudids_sorted = sorted(list(self.diplomes_ids))
self.nom = self.get_repr()
"""Représentation textuelle de l'interclassement"""
# Synthétise les moyennes/classements par tag
self.moyennes_tags: dict[str, pe_moytag.MoyennesTag] = {}
for tag in self.tags_sorted:
notes = self.compute_notes_matrice(tag, etudids_sorted, self.champs_sorted)
coeffs = self.compute_coeffs_matrice(
tag, etudids_sorted, self.champs_sorted
)
self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag,
self.type,
notes,
coeffs, # limite les moyennes aux étudiants de la promo
)
def get_repr(self) -> str:
"""Une représentation textuelle"""
return f"{self.nom_rcs} par {self.type}"
def _do_taglist(self):
"""Synthétise les tags à partir des TableTags (SXTag ou RCSTag)
Returns:
Une liste de tags triés par ordre alphabétique
"""
tags = []
for rcstag in self.rcstags.values():
tags.extend(rcstag.tags_sorted)
return sorted(set(tags))
def compute_notes_matrice(
self, tag, etudids_sorted: list[int], champs_sorted: list[str]
) -> pd.DataFrame:
"""Construit la matrice de notes (etudids x champs) en
reportant les moyennes obtenues par les étudiants
aux semestres de l'aggrégat pour le tag visé.
Les champs peuvent être des acronymes d'UEs ou des compétences.
Args:
etudids_sorted: Les etudids des étudiants (diplômés) triés
champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
Return:
Le dataFrame (etudids x champs)
reportant les moyennes des étudiants aux champs
"""
# Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids_sorted, columns=champs_sorted)
for rcstag in self.rcstags.values():
# Charge les moyennes au tag d'un RCStag
if tag in rcstag.moyennes_tags:
moytag: pd.DataFrame = rcstag.moyennes_tags[tag].matrice_notes
# Etudiants/Champs communs entre le RCSTag et les données interclassées
(
etudids_communs,
champs_communs,
) = pe_comp.find_index_and_columns_communs(df, moytag)
# Injecte les notes par tag
df.loc[etudids_communs, champs_communs] = moytag.loc[
etudids_communs, champs_communs
]
return df
def compute_coeffs_matrice(
self, tag, etudids_sorted: list[int], champs_sorted: list[str]
) -> pd.DataFrame:
"""Idem que compute_notes_matrices mais pour les coeffs
Args:
etudids_sorted: Les etudids des étudiants (diplômés) triés
champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
Return:
Le dataFrame (etudids x champs)
reportant les moyennes des étudiants aux champs
"""
# Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids_sorted, columns=champs_sorted)
for rcstag in self.rcstags.values():
if tag in rcstag.moyennes_tags:
# Charge les coeffs au tag d'un RCStag
coeffs: pd.DataFrame = rcstag.moyennes_tags[tag].matrice_notes
# Etudiants/Champs communs entre le RCSTag et les données interclassées
(
etudids_communs,
champs_communs,
) = pe_comp.find_index_and_columns_communs(df, coeffs)
# Injecte les coeffs par tag
df.loc[etudids_communs, champs_communs] = coeffs.loc[
etudids_communs, champs_communs
]
return df
def _do_ues_ou_competences_list(self) -> list[str]:
"""Synthétise les champs (UEs ou compétences) sur lesquels
sont calculés les moyennes.
Returns:
Un dictionnaire {'acronyme_ue' : 'compétences'}
"""
dict_champs = []
for rcstag in self.rcstags.values():
if isinstance(rcstag, pe_sxtag.SxTag):
champs = rcstag.acronymes_sorted
else: # pe_rcstag.RCSTag
champs = rcstag.competences_sorted
dict_champs.extend(champs)
return sorted(set(dict_champs))
def has_tags(self):
"""Indique si l'interclassement a des tags (cas d'un
interclassement sur un S5 qui n'a pas eu lieu)
"""
return len(self.tags_sorted) > 0
def _un_rcstag_significatif(self, rcsstags: dict[(str, int):pe_tabletags]):
"""Renvoie un rcstag significatif (ayant des tags et des notes aux tags)
parmi le dictionnaire de rcsstags"""
for rcstag_id, rcstag in rcsstags.items():
moystags: pe_moytag.MoyennesTag = rcstag.moyennes_tags
for tag, moystag in moystags.items():
tags_tries = moystag.get_all_significant_tags()
if tags_tries:
return moystag
return None
def compute_df_synthese_moyennes_tag(
self, tag, aggregat=None, type_colonnes=False
) -> pd.DataFrame:
"""Construit le dataframe retraçant pour les données des moyennes
pour affichage dans la synthèse du jury PE. (cf. to_df())
Args:
etudids_sorted: Les etudids des étudiants (diplômés) triés
champs_sorted: Les champs (UE ou compétences) à faire apparaitre dans la matrice
Return:
Le dataFrame (etudids x champs)
reportant les moyennes des étudiants aux champs
"""
if aggregat:
assert (
aggregat == self.nom_rcs
), "L'interclassement ciblé ne correspond pas à l'aggrégat visé"
etudids_sorted = sorted(list(self.diplomes_ids))
if self.rcstags:
return None
# Un rcstag significatif pour initier les colonnes
moytag = self._un_rcstag_significatif(self.rcstags)
if not moytag:
return None
df_moytag = moytag.to_df(
aggregat=aggregat,
cohorte="Groupe",
)
colonnes = list(df_moytag.columns)
# Partant d'un dataframe vierge
df = pd.DataFrame(index=etudids_sorted, columns=colonnes) # colonnes)
for col in colonnes:
if "rang" in col:
df[col] = df[col].astype(str)
df.columns = list(range(len(colonnes)))
for rcstag in self.rcstags.values():
# Charge les moyennes au tag d'un RCStag (SemX ou RCSXTag)
if tag in rcstag.moyennes_tags:
# Les infos sur les moyennes du tag
moytag: pe_moytag.MoyennesTag = rcstag.moyennes_tags[tag]
df_moytag = moytag.to_df(
aggregat=aggregat,
cohorte="Groupe",
)
df_moytag.columns = list(range(len(colonnes)))
# Etudiants/Champs communs entre le df et les données interclassées
(
etudids_communs,
champs_communs, # les colonnes de synthèse
) = pe_comp.find_index_and_columns_communs(df, df_moytag)
# Injecte les données par tag
df.loc[etudids_communs, champs_communs] = df_moytag.loc[
etudids_communs, champs_communs
]
# Refixe les colonnes
df.columns = colonnes
return df

View File

@ -1,13 +1,12 @@
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from app import comp
from app.comp.moy_sem import comp_ranks_series from app.comp.moy_sem import comp_ranks_series
from app.pe import pe_affichage from app.pe import pe_affichage
class Moyenne: class Moyenne:
CRITERES = [ COLONNES = [
"note", "note",
"classement", "classement",
"rang", "rang",
@ -17,6 +16,9 @@ class Moyenne:
"nb_etuds", "nb_etuds",
"nb_inscrits", "nb_inscrits",
] ]
"""Colonnes du df"""
COLONNES_SYNTHESE = ["note", "rang", "min", "max", "moy"]
def __init__(self, notes: pd.Series): def __init__(self, notes: pd.Series):
"""Classe centralisant la synthèse des moyennes/classements d'une série """Classe centralisant la synthèse des moyennes/classements d'une série
@ -58,7 +60,7 @@ class Moyenne:
df = pd.DataFrame( df = pd.DataFrame(
np.nan, np.nan,
index=self.etudids, index=self.etudids,
columns=Moyenne.CRITERES, columns=Moyenne.COLONNES,
) )
# Supprime d'éventuelles chaines de caractères dans les notes # Supprime d'éventuelles chaines de caractères dans les notes
@ -67,14 +69,18 @@ class Moyenne:
# Les nb d'étudiants & nb d'inscrits # Les nb d'étudiants & nb d'inscrits
df["nb_etuds"] = len(self.etudids) df["nb_etuds"] = len(self.etudids)
df["nb_etuds"] = df["nb_etuds"].astype(int)
# Les étudiants dont la note n'est pas nulle # Les étudiants dont la note n'est pas nulle
inscrits_ids = notes[notes.notnull()].index.to_list() inscrits_ids = notes[notes.notnull()].index.to_list()
df.loc[inscrits_ids, "nb_inscrits"] = len(inscrits_ids) df.loc[inscrits_ids, "nb_inscrits"] = len(inscrits_ids)
# df["nb_inscrits"] = df["nb_inscrits"].astype(int)
# Le classement des inscrits # Le classement des inscrits
notes_non_nulles = notes[inscrits_ids] notes_non_nulles = notes[inscrits_ids]
(class_str, class_int) = comp_ranks_series(notes_non_nulles) (class_str, class_int) = comp_ranks_series(notes_non_nulles)
df.loc[inscrits_ids, "classement"] = class_int df.loc[inscrits_ids, "classement"] = class_int
# df["classement"] = df["classement"].astype(int)
# Le rang (classement/nb_inscrit) # Le rang (classement/nb_inscrit)
df["rang"] = df["rang"].astype(str) df["rang"] = df["rang"].astype(str)
@ -91,6 +97,10 @@ class Moyenne:
return df return df
def get_df_synthese(self):
"""Renvoie le df de synthese limité aux colonnes de synthese"""
return self.df[self.COLONNES_SYNTHESE]
def to_dict(self) -> dict: def to_dict(self) -> dict:
"""Renvoie un dictionnaire de synthèse des moyennes/classements/statistiques générale (but)""" """Renvoie un dictionnaire de synthèse des moyennes/classements/statistiques générale (but)"""
synthese = { synthese = {
@ -103,10 +113,6 @@ class Moyenne:
} }
return synthese return synthese
def get_notes(self):
"""Série des notes, arrondies à 2 chiffres après la virgule"""
return self.df_gen["note"].round(2)
def get_rangs_inscrits(self) -> pd.Series: def get_rangs_inscrits(self) -> pd.Series:
"""Série des rangs classement/nbre_inscrit""" """Série des rangs classement/nbre_inscrit"""
return self.df_gen["rang"] return self.df_gen["rang"]
@ -151,91 +157,3 @@ class Moyenne:
def is_significatif(self) -> bool: def is_significatif(self) -> bool:
"""Indique si la moyenne est significative (c'est-à-dire à des notes)""" """Indique si la moyenne est significative (c'est-à-dire à des notes)"""
return self.synthese["nb_inscrits"] > 0 return self.synthese["nb_inscrits"] > 0
class MoyennesTag:
def __init__(
self,
tag: str,
matrice_notes: pd.DataFrame, # etudids x colonnes
matrice_coeffs: pd.DataFrame, # etudids x colonnes
):
"""Classe centralisant la synthèse des moyennes/classements d'une série
d'étudiants à un tag donné, en différenciant les notes
obtenues aux UE et au général (toutes UEs confondues)
Args:
tag: Un tag
matrice_notes: Les moyennes (etudid x acronymes_ues ou etudid x compétences) aux différentes UEs ou compétences
matrice_coeffs: Les coeff à appliquer pour le calcul de la moyenne générale
# notes_gen: Une série de notes (moyenne) sous forme d'un ``pd.Series`` (toutes UEs confondues)
"""
self.tag = tag
"""Le tag associé aux moyennes"""
# Les moyennes par UE
self.matrice_notes: pd.DataFrame = matrice_notes
"""Les notes aux UEs ou aux compétences (DataFrame)"""
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"""
self.moyennes: dict[int, pd.DataFrame] = {}
"""Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs"""
colonnes = self.matrice_notes.columns
for col in colonnes: # if ue.type != UE_SPORT:
notes = matrice_notes[col]
self.moyennes[col] = Moyenne(notes)
# Les moyennes générales
notes_gen = pd.Series(np.nan, index=self.matrice_notes.index)
"""Les notes générales (moyenne toutes UEs confonudes)"""
if self.has_notes():
notes_gen = self.compute_moy_gen(
self.matrice_notes, self.matrice_coeffs_moy_gen
)
self.notes_gen = notes_gen
self.moyenne_gen = Moyenne(notes_gen)
"""Le dataframe retraçant les moyennes/classements/statistiques général"""
def has_notes(self):
"""Détermine si les moyennes (aux UEs ou aux compétences)
ont des notes
Returns:
True si la moytag a des notes, False sinon
"""
notes = self.matrice_notes
nbre_nan = notes.isna().sum().sum()
nbre_notes_potentielles = len(notes.index) * len(notes.columns)
if nbre_nan == nbre_notes_potentielles:
return False
else:
return True
def compute_moy_gen(self, moys: pd.DataFrame, coeffs: pd.DataFrame) -> pd.Series:
"""Calcule la moyenne générale (toutes UE/compétences confondus)
pour le tag considéré, en pondérant les notes obtenues au UE
par les coeff (généralement les crédits ECTS).
Args:
moys: Les moyennes etudids x acronymes_ues/compétences
coeff: Les coeff etudids x ueids/compétences
"""
# Calcule la moyenne générale dans le semestre (pondérée par le ECTS)
try:
moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects(
moys,
coeffs.fillna(0.0),
# formation_id=self.formsemestre.formation_id,
skip_empty_ues=True,
)
except TypeError as e:
raise TypeError(
"Pb dans le calcul de la moyenne toutes UEs/compétences confondues"
)
return moy_gen_tag

155
app/pe/moys/pe_moytag.py Normal file
View File

@ -0,0 +1,155 @@
import numpy as np
import pandas as pd
from app import comp
from app.comp.moy_sem import comp_ranks_series
from app.pe.moys import pe_moy
CODE_MOY_UE = "UEs"
CODE_MOY_COMPETENCES = "Compétences"
CHAMP_GENERAL = "Général" # Nom du champ de la moyenne générale
class MoyennesTag:
def __init__(
self,
tag: str,
type_moyenne: str,
matrice_notes: pd.DataFrame, # etudids x colonnes
matrice_coeffs: pd.DataFrame, # etudids x colonnes
):
"""Classe centralisant la synthèse des moyennes/classements d'une série
d'étudiants à un tag donné, en différenciant les notes
obtenues aux UE et au général (toutes UEs confondues)
Args:
tag: Un tag
matrice_notes: Les moyennes (etudid x acronymes_ues ou etudid x compétences) aux différentes UEs ou compétences
matrice_coeffs: Les coeff à appliquer pour le calcul de la moyenne générale
# notes_gen: Une série de notes (moyenne) sous forme d'un ``pd.Series`` (toutes UEs confondues)
"""
self.tag = tag
"""Le tag associé aux moyennes"""
self.type = type_moyenne
"""Le type de moyennes (par UEs ou par compétences)"""
# Les moyennes par UE
self.matrice_notes: pd.DataFrame = matrice_notes
"""Les notes aux UEs ou aux compétences (DataFrame)"""
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"""
self.moyennes: dict[int, pd.DataFrame] = {}
"""Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs"""
self.etudids = self.matrice_notes.index
"""Les étudids renseignés dans les moyennes"""
self.champs = self.matrice_notes.columns
"""Les champs (acronymes d'UE ou compétences) renseignés dans les moyennes"""
for col in self.champs: # if ue.type != UE_SPORT:
notes = matrice_notes[col]
self.moyennes[col] = pe_moy.Moyenne(notes)
# Les moyennes générales
notes_gen = pd.Series(np.nan, index=self.matrice_notes.index)
"""Les notes générales (moyenne toutes UEs confonudes)"""
if self.has_notes():
notes_gen = self.compute_moy_gen(
self.matrice_notes, self.matrice_coeffs_moy_gen
)
self.notes_gen = notes_gen
self.moyenne_gen = pe_moy.Moyenne(notes_gen)
"""Le dataframe retraçant les moyennes/classements/statistiques général"""
def has_notes(self):
"""Détermine si les moyennes (aux UEs ou aux compétences)
ont des notes
Returns:
True si la moytag a des notes, False sinon
"""
notes = self.matrice_notes
nbre_nan = notes.isna().sum().sum()
nbre_notes_potentielles = len(notes.index) * len(notes.columns)
if nbre_nan == nbre_notes_potentielles:
return False
else:
return True
def compute_moy_gen(self, moys: pd.DataFrame, coeffs: pd.DataFrame) -> pd.Series:
"""Calcule la moyenne générale (toutes UE/compétences confondus)
pour le tag considéré, en pondérant les notes obtenues au UE
par les coeff (généralement les crédits ECTS).
Args:
moys: Les moyennes etudids x acronymes_ues/compétences
coeff: Les coeff etudids x ueids/compétences
"""
# Calcule la moyenne générale dans le semestre (pondérée par le ECTS)
try:
moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects(
moys,
coeffs.fillna(0.0),
# formation_id=self.formsemestre.formation_id,
skip_empty_ues=True,
)
except TypeError as e:
raise TypeError(
"Pb dans le calcul de la moyenne toutes UEs/compétences confondues"
)
return moy_gen_tag
def to_df(self, aggregat=None, cohorte=None) -> pd.DataFrame:
"""Renvoie le df synthétisant l'ensemble des données
connues
Adapte les intitulés des colonnes aux données fournies
(nom d'aggrégat, type de cohorte).
"""
etudids_sorted = sorted(self.etudids)
df = pd.DataFrame(index=etudids_sorted)
# Ajout des notes pour tous les champs
champs = list(self.champs)
for champ in champs:
df_champ = self.moyennes[champ].get_df_synthese() # le dataframe
# Renomme les colonnes
cols = [
get_colonne_df(aggregat, self.tag, champ, cohorte, critere)
for critere in pe_moy.Moyenne.COLONNES_SYNTHESE
]
df_champ.columns = cols
df = df.join(df_champ)
# Ajoute la moy générale
df_moy_gen = self.moyenne_gen.get_df_synthese()
cols = [
get_colonne_df(aggregat, self.tag, CHAMP_GENERAL, cohorte, critere)
for critere in pe_moy.Moyenne.COLONNES_SYNTHESE
]
df_moy_gen.columns = cols
df = df.join(df_moy_gen)
return df
def get_colonne_df(aggregat, tag, champ, cohorte, critere):
"""Renvoie le tuple (aggregat, tag, champ, cohorte, critere)
utilisé pour désigner les colonnes du df"""
liste_champs = []
if aggregat:
liste_champs += [aggregat]
liste_champs += [tag, champ]
if cohorte:
liste_champs += [cohorte]
liste_champs += [critere]
return tuple(liste_champs)

View File

@ -36,19 +36,17 @@ Created on Fri Sep 9 09:15:05 2016
@author: barasc @author: barasc
""" """
from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre from app.models import FormSemestre
from app.pe import pe_affichage from app.pe import pe_affichage
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from app.pe.rcss import pe_rcs, pe_rcsemx from app.pe.rcss import pe_rcs, pe_rcsemx
import app.pe.pe_sxtag as pe_sxtag import app.pe.moys.pe_sxtag as pe_sxtag
import app.pe.pe_comp as pe_comp import app.pe.pe_comp as pe_comp
from app.pe.pe_tabletags import TableTag from app.pe.moys import pe_tabletags, pe_moytag
from app.pe.pe_moytag import MoyennesTag
class RCSTag(TableTag): class RCSTag(pe_tabletags.TableTag):
def __init__( def __init__(
self, rcsemx: pe_rcsemx.RCSemX, sxstags: dict[(str, int) : pe_sxtag.SxTag] self, rcsemx: pe_rcsemx.RCSemX, sxstags: dict[(str, int) : pe_sxtag.SxTag]
): ):
@ -62,7 +60,7 @@ class RCSTag(TableTag):
rcsemx: Le RCSemX (identifié par un nom et l'id de son semestre terminal) rcsemx: Le RCSemX (identifié par un nom et l'id de son semestre terminal)
sxstags: Les données sur les SemX taggués sxstags: Les données sur les SemX taggués
""" """
TableTag.__init__(self) pe_tabletags.TableTag.__init__(self)
self.rcs_id: tuple(str, int) = rcsemx.rcs_id self.rcs_id: tuple(str, int) = rcsemx.rcs_id
"""Identifiant du RCSTag (identique au RCSemX sur lequel il s'appuie)""" """Identifiant du RCSTag (identique au RCSemX sur lequel il s'appuie)"""
@ -121,10 +119,9 @@ class RCSTag(TableTag):
pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}") 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, pe_moytag.MoyennesTag] = {}
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)""" """Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
for tag in self.tags_sorted: for tag in self.tags_sorted:
print(tag)
# Cube de notes (etudids_sorted x compétences_sorted x sxstags) # Cube de notes (etudids_sorted x compétences_sorted x sxstags)
notes_df, notes_cube = self.compute_notes_comps_cube( notes_df, notes_cube = self.compute_notes_comps_cube(
tag, self.etudids_sorted, self.competences_sorted, self.sxstags tag, self.etudids_sorted, self.competences_sorted, self.sxstags
@ -147,8 +144,11 @@ class RCSTag(TableTag):
coeffs_cube, notes_cube, self.etudids_sorted, self.competences_sorted coeffs_cube, notes_cube, self.etudids_sorted, self.competences_sorted
) )
# Mémorise les moyennes et les coeff associés # Mémorise les moyennes et les coeff associés
self.moyennes_tags[tag] = MoyennesTag( self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag, moys_competences, matrice_coeffs_moy_gen tag,
pe_moytag.CODE_MOY_COMPETENCES,
moys_competences,
matrice_coeffs_moy_gen,
) )
def __eq__(self, other): def __eq__(self, other):

View File

@ -37,16 +37,13 @@ Created on Fri Sep 9 09:15:05 2016
""" """
import pandas as pd import pandas as pd
from app import db, ScoValueError from app import ScoValueError
from app import comp from app import comp
from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre, UniteEns from app.models import FormSemestre, UniteEns
from app.models.moduleimpls import ModuleImpl
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_tabletags as pe_tabletags from app.pe.moys import pe_tabletags, pe_moytag
from app.pe.pe_moytag import MoyennesTag
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.scodoc import codes_cursus as sco_codes from app.scodoc import codes_cursus as sco_codes
@ -126,17 +123,20 @@ 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)
self.moyennes_tags[tag] = MoyennesTag( self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag, moy_ues_tag, self.matrice_coeffs_moy_gen tag, pe_moytag.CODE_MOY_UE, moy_ues_tag, self.matrice_coeffs_moy_gen
) )
# Ajoute les moyennes par UEs + la moyenne générale (but) # Ajoute les moyennes par UEs + la moyenne générale (but)
moy_gen = self.compute_moy_gen() moy_gen = self.compute_moy_gen()
self.moyennes_tags["but"] = MoyennesTag( self.moyennes_tags["but"] = pe_moytag.MoyennesTag(
"but", moy_gen, self.matrice_coeffs_moy_gen # , moy_gen_but "but",
pe_moytag.CODE_MOY_UE,
moy_gen,
self.matrice_coeffs_moy_gen, # , moy_gen_but
) )
self.tags_sorted = self.get_all_tags() self.tags_sorted = self.get_all_significant_tags()
"""Tags (personnalisés+compétences) par ordre alphabétique""" """Tags (personnalisés+compétences) par ordre alphabétique"""
def get_repr(self, verbose=False) -> str: def get_repr(self, verbose=False) -> str:
@ -302,11 +302,12 @@ 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
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
aff_tags_perso = ", ".join([f"👜{nom}" for nom in noms_tags_perso])
# Affichage # Affichage
pe_affichage.pe_print( pe_affichage.pe_print(
f"* Tags du programme de formation : {', '.join(noms_tags_perso)} " f"* Tags du programme de formation : {aff_tags_perso} + "
+ f"Tags automatiques : {', '.join(noms_tags_auto)}" + f"Tags automatiques : {aff_tags_auto}"
) )
return dict_tags return dict_tags

View File

@ -37,16 +37,15 @@ Created on Fri Sep 9 09:15:05 2016
""" """
from app.pe import pe_affichage, pe_comp from app.pe import pe_affichage, pe_comp
import app.pe.pe_ressemtag as pe_ressemtag import app.pe.moys.pe_ressemtag as pe_ressemtag
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.moys import pe_moytag, pe_tabletags
from app.pe.pe_moytag import MoyennesTag
import app.pe.rcss.pe_trajectoires as pe_trajectoires import app.pe.rcss.pe_trajectoires as pe_trajectoires
class SxTag(TableTag): class SxTag(pe_tabletags.TableTag):
def __init__( def __init__(
self, self,
sxtag_id: (str, int), sxtag_id: (str, int),
@ -80,7 +79,7 @@ class SxTag(TableTag):
les semestres à regrouper et les résultats/moyennes par tag des les semestres à regrouper et les résultats/moyennes par tag des
semestres semestres
""" """
TableTag.__init__(self) pe_tabletags.TableTag.__init__(self)
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
@ -186,8 +185,11 @@ class SxTag(TableTag):
self.__aff_profil_coeff_ects(tag) self.__aff_profil_coeff_ects(tag)
# Mémorise les infos pour la moyennes au tag # Mémorise les infos pour la moyennes au tag
self.moyennes_tags[tag] = MoyennesTag( self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag, matrice_moys_ues, self.matrice_coeffs_moy_gen tag,
pe_moytag.CODE_MOY_UE,
matrice_moys_ues,
self.matrice_coeffs_moy_gen,
) )
def __aff_profil_coeff_ects(self, tag): def __aff_profil_coeff_ects(self, tag):

204
app/pe/moys/pe_tabletags.py Normal file
View File

@ -0,0 +1,204 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
"""
Created on Thu Sep 8 09:36:33 2016
@author: barasc
"""
import pandas as pd
from app.models import Identite
from app.pe.moys import pe_moytag
TAGS_RESERVES = ["but"]
CHAMPS_ADMINISTRATIFS = ["Civilité", "Nom", "Prénom"]
class TableTag(object):
def __init__(self):
"""Classe centralisant différentes méthodes communes aux
SemestreTag, TrajectoireTag, AggregatInterclassTag
"""
# Les étudiants
# self.etuds: list[Identite] = None # A venir
"""Les étudiants"""
# self.etudids: list[int] = {}
"""Les etudids"""
def add_etuds(self, etuds: list[Identite]):
"""Mémorise les informations sur les étudiants
Args:
etuds: la liste des identités de l'étudiant
"""
# self.etuds = etuds
self.etudids = list({etud.etudid for etud in etuds})
def get_all_significant_tags(self):
"""Liste des tags de la table, triée par ordre alphabétique,
extraite des clés du dictionnaire ``moyennes_tags``, en ne
considérant que les moyennes ayant des notes.
Returns:
Liste de tags triés par ordre alphabétique
"""
tags = []
tag: str = ""
moytag: pe_moytag.MoyennesTag = None
for tag, moytag in self.moyennes_tags.items():
if moytag.has_notes():
tags.append(tag)
return sorted(tags)
def to_df(
self,
administratif=True,
aggregat=None,
tags_cibles=None,
cohorte=None,
type_colonnes=True,
) -> pd.DataFrame:
"""Renvoie un dataframe listant toutes les données
des moyennes/classements/nb_inscrits/min/max/moy
des étudiants aux différents tags.
tags_cibles limitent le dataframe aux tags indiqués
type_colonnes indiquent si les colonnes doivent être passées en multiindex
Args:
administratif: Indique si les données administratives sont incluses
aggregat: l'aggrégat représenté
tags_cibles: la liste des tags ciblés
cohorte: la cohorte représentée
Returns:
Le dataframe complet de synthèse
"""
if not self.is_significatif():
return None
# Les tags visés
tags_tries = self.get_all_significant_tags()
if not tags_cibles:
tags_cibles = tags_tries
tags_cibles = sorted(tags_cibles)
# Les tags visés avec des notes
# Les étudiants visés
if administratif:
df = df_administratif(self.etuds, aggregat, cohorte)
else:
df = pd.DataFrame(index=self.etudids)
# Ajout des données par tags
for tag in tags_cibles:
moy_tag_df = self.moyennes_tags[tag].to_df(aggregat, cohorte)
df = df.join(moy_tag_df)
# Tri par nom, prénom
if administratif:
colonnes_tries = [
_get_champ_administratif(champ, aggregat, cohorte)
for champ in CHAMPS_ADMINISTRATIFS[1:]
] # Nom + Prénom
df = df.sort_values(by=colonnes_tries)
# Conversion des colonnes en multiindex
if type_colonnes:
df.columns = pd.MultiIndex.from_tuples(df.columns)
return df
def has_etuds(self):
"""Indique si un tabletag contient des étudiants"""
return len(self.etuds) > 0
def is_significatif(self):
"""Indique si une tabletag a des données"""
# A des étudiants
if not self.etuds:
return False
# A des tags avec des notes
tags_tries = self.get_all_significant_tags()
if not tags_tries:
return False
return True
def _get_champ_administratif(champ, aggregat=None, cohorte=None):
"""Pour un champ donné, renvoie l'index (ou le multindex)
à intégrer au dataframe"""
liste = []
if aggregat:
liste += [aggregat]
liste += ["Administratif", "Identité"]
if cohorte:
liste += [champ]
liste += [champ]
return tuple(liste)
def df_administratif(
etuds: list[Identite], aggregat=None, cohorte=None
) -> pd.DataFrame:
"""Renvoie un dataframe donnant les données administratives
des étudiants du TableTag
Args:
etuds: Identité des étudiants générant les données administratives
"""
identites = {etud.etudid: etud for etud in etuds}
donnees = {}
etud: Identite = None
for etudid, etud in identites.items():
data = {
CHAMPS_ADMINISTRATIFS[0]: etud.civilite_str,
CHAMPS_ADMINISTRATIFS[1]: etud.nom,
CHAMPS_ADMINISTRATIFS[2]: etud.prenom_str,
}
donnees[etudid] = {
_get_champ_administratif(champ, aggregat, cohorte): data[champ]
for champ in CHAMPS_ADMINISTRATIFS
}
colonnes = [
_get_champ_administratif(champ, aggregat, cohorte)
for champ in CHAMPS_ADMINISTRATIFS
]
df = pd.DataFrame.from_dict(donnees, orient="index", columns=colonnes)
df = df.sort_values(by=colonnes[1:])
return df

View File

@ -1,160 +0,0 @@
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
"""
Created on Thu Sep 8 09:36:33 2016
@author: barasc
"""
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.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):
"""
Interclasse l'ensemble des étudiants diplômés à une année
donnée (celle du jury), pour un RCS donné (par ex: 'S2', '3S')
en reportant :
* les moyennes obtenues sur la trajectoire qu'il ont suivi pour atteindre
le numéro de semestre de fin de l'aggrégat (indépendamment de son
formsemestre)
* calculant le classement sur les étudiants diplômes
"""
def __init__(
self,
nom_rcs: str,
etudiants: pe_etudiant.EtudiantsJuryPE,
rcss_jury_pe: pe_rcss_jury.RCSsJuryPE,
rcss_tags: dict[tuple, pe_rcstag.RCSTag],
):
TableTag.__init__(self)
self.nom_rcs = nom_rcs
"""Le nom du RCS interclassé"""
self.nom = self.get_repr()
"""Représentation textuelle de l'interclassement"""
self.diplomes_ids = etudiants.etudiants_diplomes
"""Identité des étudiants diplômés"""
self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids}
"""Etudids des étudiants diplômés"""
# Les RCSemX et leur versions taggués SxTag, en ne gardant que
# celles associées à l'aggrégat
self.rcss: dict[int, pe_rcs.RCS] = {}
"""Ensemble des trajectoires associées à l'aggrégat"""
for trajectoire_id in rcss_jury_pe.trajectoires:
trajectoire = rcss_jury_pe.trajectoires[trajectoire_id]
if trajectoire_id[0] == nom_rcs:
self.rcss[trajectoire_id] = trajectoire
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, 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:
self.suivi[etudid] = rcss_jury_pe.trajectoires_suivies[etudid][nom_rcs]
self.tags_sorted = self.do_taglist()
"""Liste des tags (triés par ordre alphabétique)"""
# Construit la matrice de notes
self.notes = self.compute_notes_matrice()
"""Matrice des notes de l'aggrégat"""
# Synthétise les moyennes/classements par tag
self.moyennes_tags: dict[str, MoyennesTag] = {}
for tag in self.tags_sorted:
moy_gen_tag = self.notes[tag]
self.moyennes_tags[tag] = MoyennesTag(tag, moy_gen_tag)
# Est significatif ? (aka a-t-il des tags et des notes)
self.significatif = len(self.tags_sorted) > 0
def get_repr(self) -> str:
"""Une représentation textuelle"""
return f"Aggrégat {self.nom_rcs}"
def do_taglist(self):
"""Synthétise les tags à partir des trajectoires_tagguées
Returns:
Une liste de tags triés par ordre alphabétique
"""
tags = []
for trajectoire in self.trajectoires_taggues.values():
tags.extend(trajectoire.tags_sorted)
return sorted(set(tags))
def compute_notes_matrice(self):
"""Construit la matrice de notes (etudid x tags)
retraçant les moyennes obtenues par les étudiants dans les semestres associés à
l'aggrégat (une trajectoire ayant pour numéro de semestre final, celui de l'aggrégat).
"""
# nb_tags = len(self.tags_sorted) unused ?
# nb_etudiants = len(self.diplomes_ids)
# Index de la matrice (etudids -> dim 0, tags -> dim 1)
etudids = list(self.diplomes_ids)
tags = self.tags_sorted
# Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids, columns=tags)
for trajectoire in self.trajectoires_taggues.values():
# Charge les moyennes par tag de la trajectoire tagguée
notes = trajectoire.notes
# Etudiants/Tags communs entre la trajectoire_tagguée et les données interclassées
etudids_communs = df.index.intersection(notes.index)
tags_communs = df.columns.intersection(notes.columns)
# Injecte les notes par tag
df.loc[etudids_communs, tags_communs] = notes.loc[
etudids_communs, tags_communs
]
return df

View File

@ -51,13 +51,18 @@ import numpy as np
import pandas as pd import pandas as pd
from app.pe.rcss import pe_rcs from app.pe.rcss import pe_rcs
from app.pe import pe_sxtag from app.pe.moys 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
from app.pe.pe_rcstag import RCSTag from app.pe.moys import (
from app.pe.pe_ressemtag import ResSemBUTTag pe_tabletags,
from app.pe.pe_interclasstag import RCSInterclasseTag pe_ressemtag,
pe_sxtag,
pe_rcstag,
pe_interclasstag,
pe_moytag,
)
import app.pe.pe_rcss_jury as pe_rcss_jury import app.pe.pe_rcss_jury as pe_rcss_jury
@ -85,8 +90,7 @@ class JuryPE(object):
) )
# Chargement des étudiants à prendre en compte dans le jury # Chargement des étudiants à prendre en compte dans le jury
pe_affichage.pe_print( pe_affichage.pe_print(
f"""*** Recherche et chargement des étudiants diplômés en { f"""*** Recherche des étudiants diplômés 🎓 en {self.diplome}"""
self.diplome}"""
) )
self.etudiants = pe_etudiant.EtudiantsJuryPE( self.etudiants = pe_etudiant.EtudiantsJuryPE(
self.diplome self.diplome
@ -110,8 +114,8 @@ class JuryPE(object):
self._gen_xls_sxtags(zipfile) self._gen_xls_sxtags(zipfile)
self._gen_rcsemxs() self._gen_rcsemxs()
self._gen_xls_rcstags(zipfile) self._gen_xls_rcstags(zipfile)
# self._gen_xls_interclassements_rcss(zipfile) self._gen_xls_interclasstags(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: except Exception as e:
raise e raise e
@ -159,7 +163,7 @@ class JuryPE(object):
self.ressembuttags = {} self.ressembuttags = {}
for frmsem_id, formsemestre in formsemestres.items(): for frmsem_id, formsemestre in formsemestres.items():
# Crée le semestre_tag et exécute les calculs de moyennes # Crée le semestre_tag et exécute les calculs de moyennes
self.ressembuttags[frmsem_id] = ResSemBUTTag(formsemestre) self.ressembuttags[frmsem_id] = pe_ressemtag.ResSemBUTTag(formsemestre)
# 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()
@ -170,7 +174,7 @@ class JuryPE(object):
for res_sem_tag in self.ressembuttags.values(): for res_sem_tag in self.ressembuttags.values():
onglet = res_sem_tag.get_repr(verbose=True) onglet = res_sem_tag.get_repr(verbose=True)
onglets += [] onglets += []
df = res_sem_tag.df_moyennes_et_classements() df = res_sem_tag.to_df()
# é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)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
@ -219,7 +223,6 @@ class JuryPE(object):
for rcf_id, rcf in self.rcss_jury.semXs.items(): for rcf_id, rcf in self.rcss_jury.semXs.items():
# SxTag traduisant le RCF # SxTag traduisant le RCF
sxtag_id = rcf_id sxtag_id = rcf_id
self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, rcf, self.ressembuttags) self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, rcf, self.ressembuttags)
# Intègre le bilan des semestres taggués au zip final # Intègre le bilan des semestres taggués au zip final
@ -230,20 +233,21 @@ class JuryPE(object):
onglets = [] onglets = []
for sxtag in self.sxtags.values(): for sxtag in self.sxtags.values():
onglet = sxtag.get_repr(verbose=False) onglet = sxtag.get_repr(verbose=False)
onglets += [onglet] if sxtag.is_significatif():
df = sxtag.df_moyennes_et_classements() df = sxtag.to_df()
# écriture dans l'onglet onglets += [onglet]
df.to_excel(writer, onglet, index=True, header=True) # écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0) output.seek(0)
if onglets:
self.add_file_to_zip( self.add_file_to_zip(
zipfile, zipfile,
f"semestres_taggues_{self.diplome}.xlsx", f"semestres_taggues_{self.diplome}.xlsx",
output.read(), output.read(),
path="details", path="details",
) )
def _gen_rcsemxs(self): def _gen_rcsemxs(self):
"""Génère les regroupements cohérents de RCFs qu'ont suivi chaque étudiant""" """Génère les regroupements cohérents de RCFs qu'ont suivi chaque étudiant"""
@ -279,7 +283,7 @@ class JuryPE(object):
self.rcss_tags = {} self.rcss_tags = {}
for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items(): for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items():
self.rcss_tags[rcs_id] = RCSTag(rcsemx, self.sxtags) self.rcss_tags[rcs_id] = pe_rcstag.RCSTag(rcsemx, self.sxtags)
# Intègre le bilan des trajectoires tagguées au zip final # Intègre le bilan des trajectoires tagguées au zip final
output = io.BytesIO() output = io.BytesIO()
@ -289,27 +293,67 @@ class JuryPE(object):
onglets = [] onglets = []
for rcs_tag in self.rcss_tags.values(): for rcs_tag in self.rcss_tags.values():
onglet = rcs_tag.get_repr(verbose=False) onglet = rcs_tag.get_repr(verbose=False)
onglets += [onglet] if rcs_tag.is_significatif():
df = rcs_tag.df_moyennes_et_classements() df = rcs_tag.to_df()
# écriture dans l'onglet onglets += [onglet]
df.to_excel(writer, onglet, index=True, header=True) # écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0) output.seek(0)
self.add_file_to_zip( if onglets:
zipfile, self.add_file_to_zip(
f"RCRCFs_{self.diplome}.xlsx", zipfile,
output.read(), f"RCRCFs_{self.diplome}.xlsx",
path="details", output.read(),
) path="details",
)
def _gen_xls_interclassements_rcss(self, zipfile: ZipFile): def _gen_xls_interclasstags(self, zipfile: ZipFile):
"""Intègre le bilan des RCS (interclassé par promo) au zip""" """Génère les interclassements sur la promo de diplômés
# Génère les interclassements (par promo et) par (nom d') aggrégat par (nom d') aggrégat
pe_affichage.pe_print("*** Génère les interclassements") en distinguant les interclassements par accronymes d'UEs (sur les SxTag)
self.interclassements_taggues = compute_interclassements( et ceux par compétences (sur les RCSTag).
self.etudiants, self.rcss_jury, self.rcss_tags """
pe_affichage.pe_print(
"*** Génère les interclassements sur chaque type de RCS/agrgégat"
) )
self.interclasstags = {
pe_moytag.CODE_MOY_UE: {},
pe_moytag.CODE_MOY_COMPETENCES: {},
}
etudiants_diplomes = self.etudiants.etudiants_diplomes
# Les interclassements par UE
for Sx in pe_rcs.TOUS_LES_SEMESTRES:
pe_affichage.pe_print(
f"--> Interclassement par (acronyme d')UEs pour le RCS {Sx}"
)
interclass = pe_interclasstag.InterClassTag(
Sx,
pe_moytag.CODE_MOY_UE,
etudiants_diplomes,
self.rcss_jury.semXs,
self.sxtags,
self.rcss_jury.semXs_suivis,
)
self.interclasstags[pe_moytag.CODE_MOY_UE][Sx] = interclass
# Les interclassements par compétences
for nom_rcs in pe_rcs.TOUS_LES_RCS:
pe_affichage.pe_print(
f"--> Interclassement par compétences pour le RCS {nom_rcs}"
)
interclass = pe_interclasstag.InterClassTag(
nom_rcs,
pe_moytag.CODE_MOY_COMPETENCES,
etudiants_diplomes,
self.rcss_jury.rcsemxs,
self.rcss_tags,
self.rcss_jury.rcsemxs_suivis,
)
self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][nom_rcs] = interclass
# Intègre le bilan des aggrégats (interclassé par promo) au zip final # Intègre le bilan des aggrégats (interclassé par promo) au zip final
output = io.BytesIO() output = io.BytesIO()
@ -317,23 +361,29 @@ class JuryPE(object):
output, engine="openpyxl" output, engine="openpyxl"
) as writer: ) as writer:
onglets = [] onglets = []
for interclass_tag in self.interclassements_taggues.values(): for type_interclass in [
if interclass_tag.significatif: # Avec des notes pe_moytag.CODE_MOY_UE,
onglet = interclass_tag.get_repr() pe_moytag.CODE_MOY_COMPETENCES,
onglets += [onglet] ]:
df = interclass_tag.df_moyennes_et_classements() interclasstag = self.interclasstags[type_interclass]
# écriture dans l'onglet for nom_rcs, interclass in interclasstag.items():
df.to_excel(writer, onglet, index=True, header=True) onglet = interclass.get_repr()
if interclass.is_significatif():
df = interclass.to_df(cohorte="Promo")
onglets += [onglet]
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}")
output.seek(0) output.seek(0)
self.add_file_to_zip( if onglets:
zipfile, self.add_file_to_zip(
f"interclassements_taggues_{self.diplome}.xlsx", zipfile,
output.read(), f"InterClassTags_{self.diplome}.xlsx",
path="details", output.read(),
) path="details",
)
def _gen_xls_synthese_jury_par_tag(self, zipfile: ZipFile): def _gen_xls_synthese_jury_par_tag(self, zipfile: ZipFile):
"""Synthèse des éléments du jury PE tag par tag""" """Synthèse des éléments du jury PE tag par tag"""
@ -347,7 +397,8 @@ class JuryPE(object):
output, engine="openpyxl" output, engine="openpyxl"
) as writer: ) as writer:
onglets = [] onglets = []
for onglet, df in self.synthese.items(): for (tag, type_moy), df in self.synthese.items():
onglet = f"{tag} {type_moy}"
onglets += [onglet] onglets += [onglet]
# é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)
@ -407,15 +458,18 @@ class JuryPE(object):
self.zipdata.seek(0) self.zipdata.seek(0)
return self.zipdata return self.zipdata
def do_tags_list(self, interclassements: dict[str, RCSInterclasseTag]): def _do_tags_list(self, interclassements: dict[str, dict]):
"""La liste des tags extraites des interclassements""" """La liste des tags extraites des interclassements"""
tags = [] tags = []
for aggregat in interclassements: # Pour chaque type d'interclassements
interclass = interclassements[aggregat] for type in interclassements:
if interclass.tags_sorted: interclassement = interclassements[type]
tags.extend(interclass.tags_sorted) for aggregat in interclassement:
tags = sorted(set(tags)) interclass = interclassement[aggregat]
return tags if interclass.tags_sorted:
tags.extend(interclass.tags_sorted)
tags = sorted(set(tags))
return tags
# **************************************************************************************************************** # # **************************************************************************************************************** #
# Méthodes pour la synthèse du juryPE # Méthodes pour la synthèse du juryPE
@ -423,154 +477,79 @@ class JuryPE(object):
def synthetise_jury_par_tags(self) -> dict[pd.DataFrame]: def synthetise_jury_par_tags(self) -> dict[pd.DataFrame]:
"""Synthétise tous les résultats du jury PE dans des dataframes, """Synthétise tous les résultats du jury PE dans des dataframes,
dont les onglets sont les tags""" dont les onglets sont les tags et des types de calcul de moyennes
(par UEs ou par compétences)"""
pe_affichage.pe_print("*** Synthèse finale des moyennes par tag***") pe_affichage.pe_print(
"*** Synthèse finale des moyennes par tag et par type de moyennes (UEs ou Compétences)***"
)
synthese = {} synthese = {}
pe_affichage.pe_print(" -> Synthèse des données administratives") pe_affichage.pe_print(" -> Synthèse des données administratives")
synthese["administratif"] = self.etudiants.df_administratif(self.diplomes_ids) synthese["administratif"] = self.etudiants.df_administratif(self.diplomes_ids)
tags = self.do_tags_list(self.interclassements_taggues) tags = self._do_tags_list(self.interclasstags)
for tag in tags: for tag in tags:
pe_affichage.pe_print(f" -> Synthèse du tag {tag}") for type_moy in [pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_COMPETENCES]:
synthese[tag] = self.df_tag(tag) pe_affichage.pe_print(f" -> Synthèse du tag {tag} par {type_moy}")
synthese[(tag, type_moy)] = self.df_tag_type(tag, type_moy)
return synthese return synthese
def df_tag(self, tag): def df_tag_type(self, tag, type_moy, type_colonnes=True):
"""Génère le DataFrame synthétisant les moyennes/classements (groupe, """Génère le DataFrame synthétisant les moyennes/classements (groupe +
interclassement promo) pour tous les aggrégats prévus, interclassement promo) pour tous les aggrégats prévus, en fonction
du type (UEs ou Compétences) de données souhaitées,
tels que fourni dans l'excel final. tels que fourni dans l'excel final.
Si type=UEs => tous les sxtag du tag
Si type=Compétences => tous les rcstag du tag
Args: Args:
tag: Un des tags (a minima `but`) tag: Un des tags (a minima `but`)
type_moy: Un type de moyenne
Returns: Returns:
""" """
etudids = list(self.diplomes_ids)
# Les données des étudiants # Les données des étudiants
donnees_etudiants = {} etuds = [etud for etudid, etud in self.etudiants.etudiants_diplomes.items()]
for etudid in etudids: df = pe_tabletags.df_administratif(etuds, aggregat="", cohorte="")
etudiant = self.etudiants.identites[etudid]
donnees_etudiants[etudid] = {
("Identité", "", "Civilite"): etudiant.civilite_str,
("Identité", "", "Nom"): etudiant.nom,
("Identité", "", "Prenom"): etudiant.prenom,
}
df_synthese = pd.DataFrame.from_dict(donnees_etudiants, orient="index")
# Ajout des aggrégats if type_moy == pe_moytag.CODE_MOY_UE:
for aggregat in pe_rcs.TOUS_LES_RCS: aggregats = pe_rcs.TOUS_LES_SEMESTRES
descr = app.pe.rcss.pe_rcs.TYPES_RCS[aggregat]["descr"] else:
aggregats = pe_rcs.TOUS_LES_RCS
# Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag for aggregat in aggregats:
# considéré print(aggregat)
trajectoires_tagguees = [] # Descr de l'aggrégat
for etudid in etudids: descr = pe_rcs.TYPES_RCS[aggregat]["descr"]
trajectoire = self.rcss_jury.trajectoires_suivies[etudid][aggregat]
if trajectoire:
tid = trajectoire.sxtag_id
trajectoire_tagguee = self.rcss_tags[tid]
if (
tag in trajectoire_tagguee.moyennes_tags
and trajectoire_tagguee not in trajectoires_tagguees
):
trajectoires_tagguees.append(trajectoire_tagguee)
# Combien de notes vont être injectées ? # L'interclassement associé
nbre_notes_injectees = 0 interclass = self.interclasstags[type_moy][aggregat]
for traj in trajectoires_tagguees:
moy_traj = traj.moyennes_tags[tag]
inscrits_traj = moy_traj.inscrits_ids
etudids_communs = set(etudids) & set(inscrits_traj)
nbre_notes_injectees += len(etudids_communs)
# Si l'aggrégat est significatif (aka il y a des notes) if interclass.is_significatif():
if nbre_notes_injectees > 0: # Le dataframe du classement sur le groupe
# Ajout des données classements & statistiques df_groupe = interclass.compute_df_synthese_moyennes_tag(
nom_stat_promo = f"{NOM_STAT_PROMO} {self.diplome}" tag, aggregat=aggregat, type_colonnes=False
donnees = pd.DataFrame(
index=etudids,
columns=[
[descr] * (1 + 4 * 2),
[""] + [NOM_STAT_GROUPE] * 4 + [nom_stat_promo] * 4,
["note"] + ["class.", "min", "moy", "max"] * 2,
],
) )
df = df.join(df_groupe)
for traj in trajectoires_tagguees: # Le dataframe du classement sur la promo
# Les données des trajectoires_tagguees df_promo = interclass.to_df(
moy_traj = traj.moyennes_tags[tag] administratif=False,
aggregat=aggregat,
tags_cibles=[tag],
cohorte="Promo",
type_colonnes=False,
)
df = df.join(df_promo)
# Les étudiants communs entre tableur de synthèse et trajectoires # Conversion des colonnes en multiindex
inscrits_traj = moy_traj.inscrits_ids if type_colonnes:
etudids_communs = list(set(etudids) & set(inscrits_traj)) df.columns = pd.MultiIndex.from_tuples(df.columns)
return df
# Les notes # Fin de l'aggrégat
champ = (descr, "", "note")
notes_traj = moy_traj.get_notes()
donnees.loc[etudids_communs, champ] = notes_traj.loc[
etudids_communs
]
# Les rangs
champ = (descr, NOM_STAT_GROUPE, "class.")
rangs = moy_traj.get_rangs_inscrits()
donnees.loc[etudids_communs, champ] = rangs.loc[etudids_communs]
# Les mins
champ = (descr, NOM_STAT_GROUPE, "min")
mins = moy_traj.get_min()
donnees.loc[etudids_communs, champ] = mins.loc[etudids_communs]
# Les max
champ = (descr, NOM_STAT_GROUPE, "max")
maxs = moy_traj.get_max()
donnees.loc[etudids_communs, champ] = maxs.loc[etudids_communs]
# Les moys
champ = (descr, NOM_STAT_GROUPE, "moy")
moys = moy_traj.get_moy()
donnees.loc[etudids_communs, champ] = moys.loc[etudids_communs]
# Ajoute les données d'interclassement
interclass = self.interclassements_taggues[aggregat]
moy_interclass = interclass.moyennes_tags[tag]
# Les étudiants communs entre tableur de synthèse et l'interclassement
inscrits_interclass = moy_interclass.inscrits_ids
etudids_communs = list(set(etudids) & set(inscrits_interclass))
# Les classements d'interclassement
champ = (descr, nom_stat_promo, "class.")
rangs = moy_interclass.get_rangs_inscrits()
donnees.loc[etudids_communs, champ] = rangs.loc[etudids_communs]
# Les mins
champ = (descr, nom_stat_promo, "min")
mins = moy_interclass.get_min()
donnees.loc[etudids_communs, champ] = mins.loc[etudids_communs]
# Les max
champ = (descr, nom_stat_promo, "max")
maxs = moy_interclass.get_max()
donnees.loc[etudids_communs, champ] = maxs.loc[etudids_communs]
# Les moys
champ = (descr, nom_stat_promo, "moy")
moys = moy_interclass.get_moy()
donnees.loc[etudids_communs, champ] = moys.loc[etudids_communs]
df_synthese = df_synthese.join(donnees)
# Fin de l'aggrégat
# Tri par nom/prénom
df_synthese.sort_values(
by=[("Identité", "", "Nom"), ("Identité", "", "Prenom")], inplace=True
)
return df_synthese
def synthetise_jury_par_etudiants(self) -> dict[pd.DataFrame]: def synthetise_jury_par_etudiants(self) -> dict[pd.DataFrame]:
"""Synthétise tous les résultats du jury PE dans des dataframes, """Synthétise tous les résultats du jury PE dans des dataframes,
@ -600,7 +579,7 @@ class JuryPE(object):
def df_synthese_etudiant(self, etudid: int) -> pd.DataFrame: def df_synthese_etudiant(self, etudid: int) -> pd.DataFrame:
"""Créé un DataFrame pour un étudiant donné par son etudid, retraçant """Créé un DataFrame pour un étudiant donné par son etudid, retraçant
toutes ses moyennes aux différents tag et aggrégats""" toutes ses moyennes aux différents tag et aggrégats"""
tags = self.do_tags_list(self.interclassements_taggues) tags = self._do_tags_list(self.interclasstags)
donnees = {} donnees = {}
@ -620,7 +599,7 @@ class JuryPE(object):
trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id] trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id]
if tag in trajectoire_tagguee.moyennes_tags: if tag in trajectoire_tagguee.moyennes_tags:
# L'interclassement # L'interclassement
interclass = self.interclassements_taggues[aggregat] interclass = self.interclasstags[aggregat]
# Injection des données dans un dictionnaire # Injection des données dans un dictionnaire
donnees[tag] |= get_dict_synthese_aggregat( donnees[tag] |= get_dict_synthese_aggregat(
@ -662,25 +641,6 @@ def get_formsemestres_etudiants(etudiants: pe_etudiant.EtudiantsJuryPE) -> dict:
return semestres return semestres
def compute_interclassements(
etudiants: pe_etudiant.EtudiantsJuryPE,
trajectoires_jury_pe: pe_rcss_jury.RCSsJuryPE,
trajectoires_tagguees: dict[tuple, pe_rcs.RCS],
):
"""Interclasse les étudiants, (nom d') aggrégat par aggrégat,
pour fournir un classement sur la promo. Le classement est établi au regard du nombre
d'étudiants ayant participé au même aggrégat.
"""
aggregats_interclasses_taggues = {}
for nom_aggregat in pe_rcs.TOUS_LES_RCS:
pe_affichage.pe_print(f" --> Interclassement {nom_aggregat}")
interclass = RCSInterclasseTag(
nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees
)
aggregats_interclasses_taggues[nom_aggregat] = interclass
return aggregats_interclasses_taggues
def get_defaut_dict_synthese_aggregat(nom_rcs: str, diplome: int) -> dict: def get_defaut_dict_synthese_aggregat(nom_rcs: str, diplome: int) -> dict:
"""Renvoie le dictionnaire de synthèse (à intégrer dans """Renvoie le dictionnaire de synthèse (à intégrer dans
un tableur excel) pour décrire les résultats d'un aggrégat un tableur excel) pour décrire les résultats d'un aggrégat
@ -723,8 +683,8 @@ def get_defaut_dict_synthese_aggregat(nom_rcs: str, diplome: int) -> dict:
def get_dict_synthese_aggregat( def get_dict_synthese_aggregat(
aggregat: str, aggregat: str,
trajectoire_tagguee: RCSTag, trajectoire_tagguee: pe_rcstag.RCSTag,
interclassement_taggue: RCSInterclasseTag, interclassement_taggue: pe_interclasstag.InterClassTag,
etudid: int, etudid: int,
tag: str, tag: str,
diplome: int, diplome: int,

View File

@ -1,122 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
"""
Created on Thu Sep 8 09:36:33 2016
@author: barasc
"""
import pandas as pd
from app.models import Identite
TAGS_RESERVES = ["but"]
class TableTag(object):
def __init__(self):
"""Classe centralisant différentes méthodes communes aux
SemestreTag, TrajectoireTag, AggregatInterclassTag
"""
# Les étudiants
# self.etuds: list[Identite] = None # A venir
"""Les étudiants"""
# self.etudids: list[int] = {}
"""Les etudids"""
def add_etuds(self, etuds: list[Identite]):
"""Mémorise les informations sur les étudiants
Args:
etuds: la liste des identités de l'étudiant
"""
# self.etuds = etuds
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,
extraite des clés du dictionnaire ``moyennes_tags`` connues (tags en doublon
possible).
Returns:
Liste de tags triés par ordre alphabétique
"""
return sorted(list(self.moyennes_tags.keys()))
def df_moyennes_et_classements(self) -> pd.DataFrame:
"""Renvoie un dataframe listant toutes les moyennes,
et les classements des étudiants pour tous les tags.
Est utilisé pour afficher le détail d'un tableau taggué
(semestres, trajectoires ou aggrégat)
Returns:
Le dataframe des notes et des classements
"""
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:
moy_tag = self.moyennes_tags[tag]
for acronyme in moy_tag.moyennes:
moy = moy_tag.moyennes[acronyme] # une moyenne
df = df.join(moy.synthese["notes"].rename(f"Moy {tag}-{acronyme}"))
df = df.join(
moy.synthese["classements"].rename(f"Class {tag}-{acronyme}")
)
moy_gen = moy_tag.moyenne_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.sort_values(by=["nom", "prenom"])
return df
def df_notes(self) -> pd.DataFrame | None:
"""Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags
Returns:
Un dataframe etudids x tag (avec tag par ordre alphabétique)
"""
tags_tries = self.get_all_tags()
if tags_tries:
dict_series = {}
for tag in tags_tries:
# Les moyennes associés au tag
moy_tag = self.moyennes_tags[tag]
dict_series[tag] = moy_tag.synthese["notes"]
df = pd.DataFrame(dict_series)
return df

View File

@ -10,7 +10,7 @@ Created on 01-2024
""" """
from app.models import FormSemestre from app.models import FormSemestre
from app.pe import pe_sxtag, pe_affichage from app.pe.moys import pe_sxtag
from app.pe.rcss import pe_rcs, pe_trajectoires from app.pe.rcss import pe_rcs, pe_trajectoires