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
12 changed files with 897 additions and 614 deletions
Showing only changes of commit 746314b2fb - Show all commits

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 pandas as pd
from app import comp
from app.comp.moy_sem import comp_ranks_series
from app.pe import pe_affichage
class Moyenne:
CRITERES = [
COLONNES = [
"note",
"classement",
"rang",
@ -17,6 +16,9 @@ class Moyenne:
"nb_etuds",
"nb_inscrits",
]
"""Colonnes du df"""
COLONNES_SYNTHESE = ["note", "rang", "min", "max", "moy"]
def __init__(self, notes: pd.Series):
"""Classe centralisant la synthèse des moyennes/classements d'une série
@ -58,7 +60,7 @@ class Moyenne:
df = pd.DataFrame(
np.nan,
index=self.etudids,
columns=Moyenne.CRITERES,
columns=Moyenne.COLONNES,
)
# Supprime d'éventuelles chaines de caractères dans les notes
@ -67,14 +69,18 @@ class Moyenne:
# Les nb d'étudiants & nb d'inscrits
df["nb_etuds"] = len(self.etudids)
df["nb_etuds"] = df["nb_etuds"].astype(int)
# Les étudiants dont la note n'est pas nulle
inscrits_ids = notes[notes.notnull()].index.to_list()
df.loc[inscrits_ids, "nb_inscrits"] = len(inscrits_ids)
# df["nb_inscrits"] = df["nb_inscrits"].astype(int)
# Le classement des inscrits
notes_non_nulles = notes[inscrits_ids]
(class_str, class_int) = comp_ranks_series(notes_non_nulles)
df.loc[inscrits_ids, "classement"] = class_int
# df["classement"] = df["classement"].astype(int)
# Le rang (classement/nb_inscrit)
df["rang"] = df["rang"].astype(str)
@ -91,6 +97,10 @@ class Moyenne:
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:
"""Renvoie un dictionnaire de synthèse des moyennes/classements/statistiques générale (but)"""
synthese = {
@ -103,10 +113,6 @@ class Moyenne:
}
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:
"""Série des rangs classement/nbre_inscrit"""
return self.df_gen["rang"]
@ -151,91 +157,3 @@ class Moyenne:
def is_significatif(self) -> bool:
"""Indique si la moyenne est significative (c'est-à-dire à des notes)"""
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
"""
from app.comp.res_sem import load_formsemestre_results
from app.models import FormSemestre
from app.pe import pe_affichage
import pandas as pd
import numpy as np
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
from app.pe.pe_tabletags import TableTag
from app.pe.pe_moytag import MoyennesTag
from app.pe.moys import pe_tabletags, pe_moytag
class RCSTag(TableTag):
class RCSTag(pe_tabletags.TableTag):
def __init__(
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)
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
"""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)}")
# 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)"""
for tag in self.tags_sorted:
print(tag)
# Cube de notes (etudids_sorted x compétences_sorted x sxstags)
notes_df, notes_cube = self.compute_notes_comps_cube(
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
)
# Mémorise les moyennes et les coeff associés
self.moyennes_tags[tag] = MoyennesTag(
tag, moys_competences, matrice_coeffs_moy_gen
self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag,
pe_moytag.CODE_MOY_COMPETENCES,
moys_competences,
matrice_coeffs_moy_gen,
)
def __eq__(self, other):

View File

@ -37,16 +37,13 @@ Created on Fri Sep 9 09:15:05 2016
"""
import pandas as pd
from app import db, ScoValueError
from app import ScoValueError
from app import comp
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.moduleimpls import ModuleImpl
import app.pe.pe_affichage as pe_affichage
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.pe.moys import pe_tabletags, pe_moytag
from app.scodoc import sco_tag_module
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}")
infos_tag = tags_dict["personnalises"][tag]
moy_ues_tag = self.compute_moy_ues_tag(infos_tag)
self.moyennes_tags[tag] = MoyennesTag(
tag, moy_ues_tag, self.matrice_coeffs_moy_gen
self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
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)
moy_gen = self.compute_moy_gen()
self.moyennes_tags["but"] = MoyennesTag(
"but", moy_gen, self.matrice_coeffs_moy_gen # , moy_gen_but
self.moyennes_tags["but"] = pe_moytag.MoyennesTag(
"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"""
def get_repr(self, verbose=False) -> str:
@ -302,11 +302,12 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
dict_tags["auto"] = {"but": {}}
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
pe_affichage.pe_print(
f"* Tags du programme de formation : {', '.join(noms_tags_perso)} "
+ f"Tags automatiques : {', '.join(noms_tags_auto)}"
f"* Tags du programme de formation : {aff_tags_perso} + "
+ f"Tags automatiques : {aff_tags_auto}"
)
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
import app.pe.pe_ressemtag as pe_ressemtag
import app.pe.moys.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
from app.pe.moys import pe_moytag, pe_tabletags
import app.pe.rcss.pe_trajectoires as pe_trajectoires
class SxTag(TableTag):
class SxTag(pe_tabletags.TableTag):
def __init__(
self,
sxtag_id: (str, int),
@ -80,7 +79,7 @@ class SxTag(TableTag):
les semestres à regrouper et les résultats/moyennes par tag des
semestres
"""
TableTag.__init__(self)
pe_tabletags.TableTag.__init__(self)
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)
# Mémorise les infos pour la moyennes au tag
self.moyennes_tags[tag] = MoyennesTag(
tag, matrice_moys_ues, self.matrice_coeffs_moy_gen
self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
tag,
pe_moytag.CODE_MOY_UE,
matrice_moys_ues,
self.matrice_coeffs_moy_gen,
)
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
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
import app.pe.pe_affichage as pe_affichage
import app.pe.pe_etudiant as pe_etudiant
from app.pe.pe_rcstag import RCSTag
from app.pe.pe_ressemtag import ResSemBUTTag
from app.pe.pe_interclasstag import RCSInterclasseTag
from app.pe.moys import (
pe_tabletags,
pe_ressemtag,
pe_sxtag,
pe_rcstag,
pe_interclasstag,
pe_moytag,
)
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
pe_affichage.pe_print(
f"""*** Recherche et chargement des étudiants diplômés en {
self.diplome}"""
f"""*** Recherche des étudiants diplômés 🎓 en {self.diplome}"""
)
self.etudiants = pe_etudiant.EtudiantsJuryPE(
self.diplome
@ -110,8 +114,8 @@ class JuryPE(object):
self._gen_xls_sxtags(zipfile)
self._gen_rcsemxs()
self._gen_xls_rcstags(zipfile)
# self._gen_xls_interclassements_rcss(zipfile)
# self._gen_xls_synthese_jury_par_tag(zipfile)
self._gen_xls_interclasstags(zipfile)
self._gen_xls_synthese_jury_par_tag(zipfile)
# self._gen_xls_synthese_par_etudiant(zipfile)
except Exception as e:
raise e
@ -159,7 +163,7 @@ class JuryPE(object):
self.ressembuttags = {}
for frmsem_id, formsemestre in formsemestres.items():
# 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
output = io.BytesIO()
@ -170,7 +174,7 @@ class JuryPE(object):
for res_sem_tag in self.ressembuttags.values():
onglet = res_sem_tag.get_repr(verbose=True)
onglets += []
df = res_sem_tag.df_moyennes_et_classements()
df = res_sem_tag.to_df()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
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():
# SxTag traduisant le RCF
sxtag_id = rcf_id
self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, rcf, self.ressembuttags)
# Intègre le bilan des semestres taggués au zip final
@ -230,20 +233,21 @@ class JuryPE(object):
onglets = []
for sxtag in self.sxtags.values():
onglet = sxtag.get_repr(verbose=False)
onglets += [onglet]
df = sxtag.df_moyennes_et_classements()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
if sxtag.is_significatif():
df = sxtag.to_df()
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)}")
output.seek(0)
self.add_file_to_zip(
zipfile,
f"semestres_taggues_{self.diplome}.xlsx",
output.read(),
path="details",
)
if onglets:
self.add_file_to_zip(
zipfile,
f"semestres_taggues_{self.diplome}.xlsx",
output.read(),
path="details",
)
def _gen_rcsemxs(self):
"""Génère les regroupements cohérents de RCFs qu'ont suivi chaque étudiant"""
@ -279,7 +283,7 @@ class JuryPE(object):
self.rcss_tags = {}
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
output = io.BytesIO()
@ -289,27 +293,67 @@ class JuryPE(object):
onglets = []
for rcs_tag in self.rcss_tags.values():
onglet = rcs_tag.get_repr(verbose=False)
onglets += [onglet]
df = rcs_tag.df_moyennes_et_classements()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
if rcs_tag.is_significatif():
df = rcs_tag.to_df()
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)}")
output.seek(0)
self.add_file_to_zip(
zipfile,
f"RCRCFs_{self.diplome}.xlsx",
output.read(),
path="details",
)
if onglets:
self.add_file_to_zip(
zipfile,
f"RCRCFs_{self.diplome}.xlsx",
output.read(),
path="details",
)
def _gen_xls_interclassements_rcss(self, zipfile: ZipFile):
"""Intègre le bilan des RCS (interclassé par promo) au zip"""
# Génère les interclassements (par promo et) par (nom d') aggrégat
pe_affichage.pe_print("*** Génère les interclassements")
self.interclassements_taggues = compute_interclassements(
self.etudiants, self.rcss_jury, self.rcss_tags
def _gen_xls_interclasstags(self, zipfile: ZipFile):
"""Génère les interclassements sur la promo de diplômés
par (nom d') aggrégat
en distinguant les interclassements par accronymes d'UEs (sur les SxTag)
et ceux par compétences (sur les RCSTag).
"""
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
output = io.BytesIO()
@ -317,23 +361,29 @@ class JuryPE(object):
output, engine="openpyxl"
) as writer:
onglets = []
for interclass_tag in self.interclassements_taggues.values():
if interclass_tag.significatif: # Avec des notes
onglet = interclass_tag.get_repr()
onglets += [onglet]
df = interclass_tag.df_moyennes_et_classements()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
for type_interclass in [
pe_moytag.CODE_MOY_UE,
pe_moytag.CODE_MOY_COMPETENCES,
]:
interclasstag = self.interclasstags[type_interclass]
for nom_rcs, interclass in interclasstag.items():
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)}")
output.seek(0)
self.add_file_to_zip(
zipfile,
f"interclassements_taggues_{self.diplome}.xlsx",
output.read(),
path="details",
)
if onglets:
self.add_file_to_zip(
zipfile,
f"InterClassTags_{self.diplome}.xlsx",
output.read(),
path="details",
)
def _gen_xls_synthese_jury_par_tag(self, zipfile: ZipFile):
"""Synthèse des éléments du jury PE tag par tag"""
@ -347,7 +397,8 @@ class JuryPE(object):
output, engine="openpyxl"
) as writer:
onglets = []
for onglet, df in self.synthese.items():
for (tag, type_moy), df in self.synthese.items():
onglet = f"{tag} {type_moy}"
onglets += [onglet]
# écriture dans l'onglet:
df.to_excel(writer, onglet, index=True, header=True)
@ -407,15 +458,18 @@ class JuryPE(object):
self.zipdata.seek(0)
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"""
tags = []
for aggregat in interclassements:
interclass = interclassements[aggregat]
if interclass.tags_sorted:
tags.extend(interclass.tags_sorted)
tags = sorted(set(tags))
return tags
# Pour chaque type d'interclassements
for type in interclassements:
interclassement = interclassements[type]
for aggregat in interclassement:
interclass = interclassement[aggregat]
if interclass.tags_sorted:
tags.extend(interclass.tags_sorted)
tags = sorted(set(tags))
return tags
# **************************************************************************************************************** #
# Méthodes pour la synthèse du juryPE
@ -423,154 +477,79 @@ class JuryPE(object):
def synthetise_jury_par_tags(self) -> dict[pd.DataFrame]:
"""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 = {}
pe_affichage.pe_print(" -> Synthèse des données administratives")
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:
pe_affichage.pe_print(f" -> Synthèse du tag {tag}")
synthese[tag] = self.df_tag(tag)
for type_moy in [pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_COMPETENCES]:
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
def df_tag(self, tag):
"""Génère le DataFrame synthétisant les moyennes/classements (groupe,
interclassement promo) pour tous les aggrégats prévus,
def df_tag_type(self, tag, type_moy, type_colonnes=True):
"""Génère le DataFrame synthétisant les moyennes/classements (groupe +
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.
Si type=UEs => tous les sxtag du tag
Si type=Compétences => tous les rcstag du tag
Args:
tag: Un des tags (a minima `but`)
type_moy: Un type de moyenne
Returns:
"""
etudids = list(self.diplomes_ids)
# Les données des étudiants
donnees_etudiants = {}
for etudid in etudids:
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")
etuds = [etud for etudid, etud in self.etudiants.etudiants_diplomes.items()]
df = pe_tabletags.df_administratif(etuds, aggregat="", cohorte="")
# Ajout des aggrégats
for aggregat in pe_rcs.TOUS_LES_RCS:
descr = app.pe.rcss.pe_rcs.TYPES_RCS[aggregat]["descr"]
if type_moy == pe_moytag.CODE_MOY_UE:
aggregats = pe_rcs.TOUS_LES_SEMESTRES
else:
aggregats = pe_rcs.TOUS_LES_RCS
# Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag
# considéré
trajectoires_tagguees = []
for etudid in etudids:
trajectoire = self.rcss_jury.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)
for aggregat in aggregats:
print(aggregat)
# Descr de l'aggrégat
descr = pe_rcs.TYPES_RCS[aggregat]["descr"]
# Combien de notes vont être injectées ?
nbre_notes_injectees = 0
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)
# L'interclassement associé
interclass = self.interclasstags[type_moy][aggregat]
# Si l'aggrégat est significatif (aka il y a des notes)
if nbre_notes_injectees > 0:
# Ajout des données classements & statistiques
nom_stat_promo = f"{NOM_STAT_PROMO} {self.diplome}"
donnees = pd.DataFrame(
index=etudids,
columns=[
[descr] * (1 + 4 * 2),
[""] + [NOM_STAT_GROUPE] * 4 + [nom_stat_promo] * 4,
["note"] + ["class.", "min", "moy", "max"] * 2,
],
if interclass.is_significatif():
# Le dataframe du classement sur le groupe
df_groupe = interclass.compute_df_synthese_moyennes_tag(
tag, aggregat=aggregat, type_colonnes=False
)
df = df.join(df_groupe)
for traj in trajectoires_tagguees:
# Les données des trajectoires_tagguees
moy_traj = traj.moyennes_tags[tag]
# Le dataframe du classement sur la promo
df_promo = interclass.to_df(
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
inscrits_traj = moy_traj.inscrits_ids
etudids_communs = list(set(etudids) & set(inscrits_traj))
# Les notes
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
# Conversion des colonnes en multiindex
if type_colonnes:
df.columns = pd.MultiIndex.from_tuples(df.columns)
return df
# Fin de l'aggrégat
def synthetise_jury_par_etudiants(self) -> dict[pd.DataFrame]:
"""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:
"""Créé un DataFrame pour un étudiant donné par son etudid, retraçant
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 = {}
@ -620,7 +599,7 @@ class JuryPE(object):
trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id]
if tag in trajectoire_tagguee.moyennes_tags:
# L'interclassement
interclass = self.interclassements_taggues[aggregat]
interclass = self.interclasstags[aggregat]
# Injection des données dans un dictionnaire
donnees[tag] |= get_dict_synthese_aggregat(
@ -662,25 +641,6 @@ def get_formsemestres_etudiants(etudiants: pe_etudiant.EtudiantsJuryPE) -> dict:
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:
"""Renvoie le dictionnaire de synthèse (à intégrer dans
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(
aggregat: str,
trajectoire_tagguee: RCSTag,
interclassement_taggue: RCSInterclasseTag,
trajectoire_tagguee: pe_rcstag.RCSTag,
interclassement_taggue: pe_interclasstag.InterClassTag,
etudid: int,
tag: str,
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.pe import pe_sxtag, pe_affichage
from app.pe.moys import pe_sxtag
from app.pe.rcss import pe_rcs, pe_trajectoires