Débute l'aggrégation des moyennes dans des RCS de type Sx (prise en compte de la meilleure des 2 UE en cas de redoublement)
This commit is contained in:
parent
267dbb6460
commit
883028216f
@ -187,7 +187,7 @@ def dept_etudiants(acronym: str):
|
|||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||||
return [etud.to_dict_short() for etud in dept.etudiants]
|
return [etud.to_dict_short() for etud in dept.etats_civils]
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/id/<int:dept_id>/etudiants")
|
@bp.route("/departement/id/<int:dept_id>/etudiants")
|
||||||
@ -200,7 +200,7 @@ def dept_etudiants_by_id(dept_id: int):
|
|||||||
Retourne la liste des étudiants d'un département d'id donné.
|
Retourne la liste des étudiants d'un département d'id donné.
|
||||||
"""
|
"""
|
||||||
dept = Departement.query.get_or_404(dept_id)
|
dept = Departement.query.get_or_404(dept_id)
|
||||||
return [etud.to_dict_short() for etud in dept.etudiants]
|
return [etud.to_dict_short() for etud in dept.etats_civils]
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/<string:acronym>/formsemestres_ids")
|
@bp.route("/departement/<string:acronym>/formsemestres_ids")
|
||||||
|
@ -36,7 +36,8 @@ Created on Thu Sep 8 09:36:33 2016
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from app.pe.pe_tabletags import TableTag, MoyenneTag
|
from app.pe.pe_tabletags import TableTag
|
||||||
|
from app.pe.pe_moytag import MoyennesTag
|
||||||
from app.pe.pe_etudiant import EtudiantsJuryPE
|
from app.pe.pe_etudiant import EtudiantsJuryPE
|
||||||
from app.pe.pe_rcs import RCS, RCSsJuryPE
|
from app.pe.pe_rcs import RCS, RCSsJuryPE
|
||||||
from app.pe.pe_rcstag import RCSTag
|
from app.pe.pe_rcstag import RCSTag
|
||||||
@ -107,10 +108,10 @@ class RCSInterclasseTag(TableTag):
|
|||||||
"""Matrice des notes de l'aggrégat"""
|
"""Matrice des notes de l'aggrégat"""
|
||||||
|
|
||||||
# Synthétise les moyennes/classements par tag
|
# Synthétise les moyennes/classements par tag
|
||||||
self.moyennes_tags: dict[str, MoyenneTag] = {}
|
self.moyennes_tags: dict[str, MoyennesTag] = {}
|
||||||
for tag in self.tags_sorted:
|
for tag in self.tags_sorted:
|
||||||
moy_gen_tag = self.notes[tag]
|
moy_gen_tag = self.notes[tag]
|
||||||
self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)
|
self.moyennes_tags[tag] = MoyennesTag(tag, moy_gen_tag)
|
||||||
|
|
||||||
# Est significatif ? (aka a-t-il des tags et des notes)
|
# Est significatif ? (aka a-t-il des tags et des notes)
|
||||||
self.significatif = len(self.tags_sorted) > 0
|
self.significatif = len(self.tags_sorted) > 0
|
||||||
|
@ -53,9 +53,9 @@ import pandas as pd
|
|||||||
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
|
||||||
from app.pe.pe_etudiant import * # TODO A éviter -> pe_etudiant.
|
from app.pe.pe_etudiant import * # TODO A éviter -> pe_etudiant.
|
||||||
from app.pe.pe_rcs import * # TODO A éviter
|
import app.pe.pe_rcs as pe_rcs
|
||||||
from app.pe.pe_rcstag import RCSTag
|
from app.pe.pe_rcstag import RCSTag
|
||||||
from app.pe.pe_semtag import SemestreTag
|
from app.pe.pe_ressemtag import ResSemTag
|
||||||
from app.pe.pe_interclasstag import RCSInterclasseTag
|
from app.pe.pe_interclasstag import RCSInterclasseTag
|
||||||
|
|
||||||
|
|
||||||
@ -96,11 +96,11 @@ class JuryPE(object):
|
|||||||
pe_affichage.pe_print("*** Aucun étudiant diplômé")
|
pe_affichage.pe_print("*** Aucun étudiant diplômé")
|
||||||
else:
|
else:
|
||||||
self._gen_xls_diplomes(zipfile)
|
self._gen_xls_diplomes(zipfile)
|
||||||
self._gen_xls_semestre_taggues(zipfile)
|
self._gen_xls_resultats_semestres_taggues(zipfile)
|
||||||
self._gen_xls_rcss_tags(zipfile)
|
# self._gen_xls_rcss_tags(zipfile)
|
||||||
self._gen_xls_interclassements_rcss(zipfile)
|
# self._gen_xls_interclassements_rcss(zipfile)
|
||||||
self._gen_xls_synthese_jury_par_tag(zipfile)
|
# self._gen_xls_synthese_jury_par_tag(zipfile)
|
||||||
self._gen_xls_synthese_par_etudiant(zipfile)
|
# self._gen_xls_synthese_par_etudiant(zipfile)
|
||||||
# et le log
|
# et le log
|
||||||
self._add_log_to_zip(zipfile)
|
self._add_log_to_zip(zipfile)
|
||||||
|
|
||||||
@ -131,19 +131,19 @@ class JuryPE(object):
|
|||||||
path="details",
|
path="details",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _gen_xls_semestre_taggues(self, zipfile: ZipFile):
|
def _gen_xls_resultats_semestres_taggues(self, zipfile: ZipFile):
|
||||||
"Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE"
|
"""Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE"""
|
||||||
pe_affichage.pe_print("*** Génère les semestres taggués")
|
pe_affichage.pe_print("*** Génère les résultats des semestres taggués")
|
||||||
self.sems_tags = compute_semestres_tag(self.etudiants)
|
self.res_sems_tags = compute_resultats_semestres_tag(self.etudiants)
|
||||||
|
|
||||||
# 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()
|
||||||
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
||||||
output, engine="openpyxl"
|
output, engine="openpyxl"
|
||||||
) as writer:
|
) as writer:
|
||||||
for formsemestretag in self.sems_tags.values():
|
for res_sem_tag in self.res_sems_tags.values():
|
||||||
onglet = formsemestretag.nom
|
onglet = res_sem_tag.get_repr()
|
||||||
df = formsemestretag.df_moyennes_et_classements()
|
df = res_sem_tag.df_moyennes_et_classements()
|
||||||
# écriture dans l'onglet
|
# écriture dans l'onglet
|
||||||
df.to_excel(writer, onglet, index=True, header=True)
|
df.to_excel(writer, onglet, index=True, header=True)
|
||||||
output.seek(0)
|
output.seek(0)
|
||||||
@ -156,20 +156,46 @@ class JuryPE(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _gen_xls_rcss_tags(self, zipfile: ZipFile):
|
def _gen_xls_rcss_tags(self, zipfile: ZipFile):
|
||||||
"""Génère les RCS (combinaisons de semestres suivis
|
"""Génère :
|
||||||
par un étudiant)
|
|
||||||
|
* les RCS (combinaisons de semestres suivis par les étudiants au sens
|
||||||
|
d'un aggrégat (par ex: '3S'))
|
||||||
|
* les RCS tagguées des RCS, en calculant les moyennes et les classements par tag
|
||||||
|
pour chacune.
|
||||||
|
|
||||||
|
Stocke le résultat dans self.rccs_tag, un dictionnaire de
|
||||||
|
la forme ``{nom_aggregat: {fid_terminal: SetTag(fid_terminal)} }``
|
||||||
|
|
||||||
|
Pour rappel : Chaque RCS est identifié par un nom d'aggrégat et par un formsemestre terminal.
|
||||||
|
|
||||||
|
Par exemple :
|
||||||
|
|
||||||
|
* combinaisons '3S' : S1+S2+S3 en prenant en compte tous les S3 qu'ont fréquenté les
|
||||||
|
étudiants du jury PE. Ces S3 marquent les formsemestre terminal de chaque combinaison.
|
||||||
|
|
||||||
|
* combinaisons 'S2' : 1 seul S2 pour des étudiants n'ayant pas redoublé, 2 pour des redoublants (dont les
|
||||||
|
notes seront moyennées sur leur 2 semestres S2). Ces combinaisons ont pour formsemestre le dernier S2 en
|
||||||
|
date (le S2 redoublé par les redoublants est forcément antérieur)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
etudiants: Les données des étudiants
|
||||||
|
semestres_tag: Les semestres tag (pour lesquels des moyennes par tag ont été calculés)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pe_affichage.pe_print(
|
pe_affichage.pe_print(
|
||||||
"*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants"
|
"*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants"
|
||||||
)
|
)
|
||||||
self.rcss = RCSsJuryPE(self.diplome)
|
self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome)
|
||||||
self.rcss.cree_rcss(self.etudiants)
|
self.rcss_jury.cree_rcss(self.etudiants)
|
||||||
|
|
||||||
# Génère les moyennes par tags des trajectoires
|
# Génère les moyennes par tags des trajectoires
|
||||||
pe_affichage.pe_print("*** Calcule les moyennes par tag des RCS possibles")
|
pe_affichage.pe_print("*** Calcule les moyennes par tag des RCS possibles")
|
||||||
self.rcss_tags = compute_trajectoires_tag(
|
|
||||||
self.rcss, self.etudiants, self.sems_tags
|
self.rcss_tags = {}
|
||||||
)
|
for rcs_id, rcs in self.rcss_jury.rcss.items():
|
||||||
|
# nom = rcs.get_repr()
|
||||||
|
self.rcss_tags[rcs_id] = RCSTag(rcs, self.res_sems_tags)
|
||||||
|
|
||||||
# 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()
|
||||||
@ -177,7 +203,7 @@ class JuryPE(object):
|
|||||||
output, engine="openpyxl"
|
output, engine="openpyxl"
|
||||||
) as writer:
|
) as writer:
|
||||||
for rcs_tag in self.rcss_tags.values():
|
for rcs_tag in self.rcss_tags.values():
|
||||||
onglet = rcs_tag.get_repr()
|
onglet = rcs_tag.get_repr(mode="short")
|
||||||
df = rcs_tag.df_moyennes_et_classements()
|
df = rcs_tag.df_moyennes_et_classements()
|
||||||
# écriture dans l'onglet
|
# écriture dans l'onglet
|
||||||
df.to_excel(writer, onglet, index=True, header=True)
|
df.to_excel(writer, onglet, index=True, header=True)
|
||||||
@ -195,7 +221,7 @@ class JuryPE(object):
|
|||||||
# Génère les interclassements (par promo et) par (nom d') aggrégat
|
# Génère les interclassements (par promo et) par (nom d') aggrégat
|
||||||
pe_affichage.pe_print("*** Génère les interclassements par aggrégat")
|
pe_affichage.pe_print("*** Génère les interclassements par aggrégat")
|
||||||
self.interclassements_taggues = compute_interclassements(
|
self.interclassements_taggues = compute_interclassements(
|
||||||
self.etudiants, self.rcss, self.rcss_tags
|
self.etudiants, self.rcss_jury, self.rcss_tags
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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
|
||||||
@ -339,14 +365,14 @@ class JuryPE(object):
|
|||||||
df_synthese = pd.DataFrame.from_dict(donnees_etudiants, orient="index")
|
df_synthese = pd.DataFrame.from_dict(donnees_etudiants, orient="index")
|
||||||
|
|
||||||
# Ajout des aggrégats
|
# Ajout des aggrégats
|
||||||
for aggregat in TOUS_LES_RCS:
|
for aggregat in pe_rcs.TOUS_LES_RCS:
|
||||||
descr = TYPES_RCS[aggregat]["descr"]
|
descr = pe_rcs.TYPES_RCS[aggregat]["descr"]
|
||||||
|
|
||||||
# Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag
|
# Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag
|
||||||
# considéré
|
# considéré
|
||||||
trajectoires_tagguees = []
|
trajectoires_tagguees = []
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
trajectoire = self.rcss.suivi[etudid][aggregat]
|
trajectoire = self.rcss_jury.suivi[etudid][aggregat]
|
||||||
if trajectoire:
|
if trajectoire:
|
||||||
tid = trajectoire.rcs_id
|
tid = trajectoire.rcs_id
|
||||||
trajectoire_tagguee = self.rcss_tags[tid]
|
trajectoire_tagguee = self.rcss_tags[tid]
|
||||||
@ -485,14 +511,14 @@ class JuryPE(object):
|
|||||||
# Une ligne pour le tag
|
# Une ligne pour le tag
|
||||||
donnees[tag] = {("", "", "tag"): tag}
|
donnees[tag] = {("", "", "tag"): tag}
|
||||||
|
|
||||||
for aggregat in TOUS_LES_RCS:
|
for aggregat in pe_rcs.TOUS_LES_RCS:
|
||||||
# Le dictionnaire par défaut des moyennes
|
# Le dictionnaire par défaut des moyennes
|
||||||
donnees[tag] |= get_defaut_dict_synthese_aggregat(
|
donnees[tag] |= get_defaut_dict_synthese_aggregat(
|
||||||
aggregat, self.diplome
|
aggregat, self.diplome
|
||||||
)
|
)
|
||||||
|
|
||||||
# La trajectoire de l'étudiant sur l'aggrégat
|
# La trajectoire de l'étudiant sur l'aggrégat
|
||||||
trajectoire = self.rcss.suivi[etudid][aggregat]
|
trajectoire = self.rcss_jury.suivi[etudid][aggregat]
|
||||||
if trajectoire:
|
if trajectoire:
|
||||||
trajectoire_tagguee = self.rcss_tags[trajectoire.rcs_id]
|
trajectoire_tagguee = self.rcss_tags[trajectoire.rcs_id]
|
||||||
if tag in trajectoire_tagguee.moyennes_tags:
|
if tag in trajectoire_tagguee.moyennes_tags:
|
||||||
@ -541,7 +567,7 @@ def get_formsemestres_etudiants(etudiants: EtudiantsJuryPE) -> dict:
|
|||||||
return semestres
|
return semestres
|
||||||
|
|
||||||
|
|
||||||
def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
|
def compute_resultats_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
|
||||||
"""Créé les semestres taggués, de type 'S1', 'S2', ..., pour un groupe d'étudiants donnés.
|
"""Créé les semestres taggués, de type 'S1', 'S2', ..., pour un groupe d'étudiants donnés.
|
||||||
Chaque semestre taggué est rattaché à l'un des FormSemestre faisant partie du cursus scolaire
|
Chaque semestre taggué est rattaché à l'un des FormSemestre faisant partie du cursus scolaire
|
||||||
des étudiants (cf. attribut etudiants.cursus).
|
des étudiants (cf. attribut etudiants.cursus).
|
||||||
@ -563,68 +589,28 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
|
|||||||
semestres_tags = {}
|
semestres_tags = {}
|
||||||
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
|
||||||
formsemestretag = SemestreTag(frmsem_id)
|
formsemestretag = ResSemTag(frmsem_id)
|
||||||
pe_affichage.pe_print(
|
|
||||||
f" --> Semestre taggué {formsemestretag.nom} sur la base de {formsemestre}"
|
|
||||||
)
|
|
||||||
# Stocke le semestre taggué
|
# Stocke le semestre taggué
|
||||||
semestres_tags[frmsem_id] = formsemestretag
|
semestres_tags[frmsem_id] = formsemestretag
|
||||||
|
|
||||||
return semestres_tags
|
return semestres_tags
|
||||||
|
|
||||||
|
|
||||||
def compute_trajectoires_tag(
|
|
||||||
trajectoires: RCSsJuryPE,
|
|
||||||
etudiants: EtudiantsJuryPE,
|
|
||||||
semestres_taggues: dict[int, SemestreTag],
|
|
||||||
):
|
|
||||||
"""Créée les trajectoires tagguées (combinaison aggrégeant plusieurs semestres au sens
|
|
||||||
d'un aggrégat (par ex: '3S')),
|
|
||||||
en calculant les moyennes et les classements par tag pour chacune.
|
|
||||||
|
|
||||||
Pour rappel : Chaque trajectoire est identifiée un nom d'aggrégat et par un formsemestre terminal.
|
|
||||||
|
|
||||||
Par exemple :
|
|
||||||
|
|
||||||
* combinaisons '3S' : S1+S2+S3 en prenant en compte tous les S3 qu'ont fréquenté les
|
|
||||||
étudiants du jury PE. Ces S3 marquent les formsemestre terminal de chaque combinaison.
|
|
||||||
|
|
||||||
* combinaisons 'S2' : 1 seul S2 pour des étudiants n'ayant pas redoublé, 2 pour des redoublants (dont les
|
|
||||||
notes seront moyennées sur leur 2 semestres S2). Ces combinaisons ont pour formsemestre le dernier S2 en
|
|
||||||
date (le S2 redoublé par les redoublants est forcément antérieur)
|
|
||||||
|
|
||||||
|
|
||||||
Args:
|
|
||||||
etudiants: Les données des étudiants
|
|
||||||
semestres_tag: Les semestres tag (pour lesquels des moyennes par tag ont été calculés)
|
|
||||||
|
|
||||||
Return:
|
|
||||||
Un dictionnaire de la forme ``{nom_aggregat: {fid_terminal: SetTag(fid_terminal)} }``
|
|
||||||
"""
|
|
||||||
trajectoires_tagguees = {}
|
|
||||||
|
|
||||||
for trajectoire_id, trajectoire in trajectoires.rcss.items():
|
|
||||||
nom = trajectoire.get_repr()
|
|
||||||
pe_affichage.pe_print(f" --> Aggrégat {nom}")
|
|
||||||
# Trajectoire_tagguee associée
|
|
||||||
trajectoire_tagguee = RCSTag(trajectoire, semestres_taggues)
|
|
||||||
# Mémorise le résultat
|
|
||||||
trajectoires_tagguees[trajectoire_id] = trajectoire_tagguee
|
|
||||||
|
|
||||||
return trajectoires_tagguees
|
|
||||||
|
|
||||||
|
|
||||||
def compute_interclassements(
|
def compute_interclassements(
|
||||||
etudiants: EtudiantsJuryPE,
|
etudiants: EtudiantsJuryPE,
|
||||||
trajectoires_jury_pe: RCSsJuryPE,
|
trajectoires_jury_pe: pe_rcs.RCSsJuryPE,
|
||||||
trajectoires_tagguees: dict[tuple, RCS],
|
trajectoires_tagguees: dict[tuple, pe_rcs.RCS],
|
||||||
):
|
):
|
||||||
"""Interclasse les étudiants, (nom d') aggrégat par aggrégat,
|
"""Interclasse les étudiants, (nom d') aggrégat par aggrégat,
|
||||||
pour fournir un classement sur la promo. Le classement est établi au regard du nombre
|
pour fournir un classement sur la promo. Le classement est établi au regard du nombre
|
||||||
d'étudiants ayant participé au même aggrégat.
|
d'étudiants ayant participé au même aggrégat.
|
||||||
"""
|
"""
|
||||||
aggregats_interclasses_taggues = {}
|
aggregats_interclasses_taggues = {}
|
||||||
for nom_aggregat in TOUS_LES_RCS:
|
for nom_aggregat in pe_rcs.TOUS_LES_RCS:
|
||||||
pe_affichage.pe_print(f" --> Interclassement {nom_aggregat}")
|
pe_affichage.pe_print(f" --> Interclassement {nom_aggregat}")
|
||||||
interclass = RCSInterclasseTag(
|
interclass = RCSInterclasseTag(
|
||||||
nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees
|
nom_aggregat, etudiants, trajectoires_jury_pe, trajectoires_tagguees
|
||||||
@ -642,7 +628,7 @@ def get_defaut_dict_synthese_aggregat(nom_rcs: str, diplome: int) -> dict:
|
|||||||
diplôme : l'année du diplôme
|
diplôme : l'année du diplôme
|
||||||
"""
|
"""
|
||||||
# L'affichage de l'aggrégat dans le tableur excel
|
# L'affichage de l'aggrégat dans le tableur excel
|
||||||
descr = get_descr_rcs(nom_rcs)
|
descr = pe_rcs.get_descr_rcs(nom_rcs)
|
||||||
|
|
||||||
nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}"
|
nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}"
|
||||||
donnees = {
|
donnees = {
|
||||||
@ -686,7 +672,7 @@ def get_dict_synthese_aggregat(
|
|||||||
à l'aggrégat donné et pour un tag donné"""
|
à l'aggrégat donné et pour un tag donné"""
|
||||||
donnees = {}
|
donnees = {}
|
||||||
# L'affichage de l'aggrégat dans le tableur excel
|
# L'affichage de l'aggrégat dans le tableur excel
|
||||||
descr = get_descr_rcs(aggregat)
|
descr = pe_rcs.get_descr_rcs(aggregat)
|
||||||
|
|
||||||
# La note de l'étudiant (chargement à venir)
|
# La note de l'étudiant (chargement à venir)
|
||||||
note = np.nan
|
note = np.nan
|
||||||
|
203
app/pe/pe_moytag.py
Normal file
203
app/pe/pe_moytag.py
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from app.comp.moy_sem import comp_ranks_series
|
||||||
|
from app.models import UniteEns
|
||||||
|
from app.pe import pe_affichage
|
||||||
|
|
||||||
|
|
||||||
|
class Moyenne:
|
||||||
|
CRITERES = [
|
||||||
|
"note",
|
||||||
|
"classement",
|
||||||
|
"rang",
|
||||||
|
"min",
|
||||||
|
"max",
|
||||||
|
"moy",
|
||||||
|
"nb_etuds",
|
||||||
|
"nb_inscrits",
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, notes: pd.Series):
|
||||||
|
"""Classe centralisant la synthèse des moyennes/classements d'une série
|
||||||
|
de notes :
|
||||||
|
|
||||||
|
* des "notes": la Serie pandas des notes (float),
|
||||||
|
* des "classements": la Serie pandas des classements (float),
|
||||||
|
* des "min": la note minimum,
|
||||||
|
* des "max": la note maximum,
|
||||||
|
* des "moy": la moyenne,
|
||||||
|
* des "nb_inscrits": le nombre d'étudiants ayant une note,
|
||||||
|
"""
|
||||||
|
self.notes = notes
|
||||||
|
"""Les notes"""
|
||||||
|
self.etudids = list(notes.index) # calcul à venir
|
||||||
|
"""Les id des étudiants"""
|
||||||
|
self.inscrits_ids = notes[notes.notnull()].index.to_list()
|
||||||
|
"""Les id des étudiants dont la note est non nulle"""
|
||||||
|
self.df: pd.DataFrame = self.comp_moy_et_stat(self.notes)
|
||||||
|
"""Le dataframe retraçant les moyennes/classements/statistiques"""
|
||||||
|
self.synthese = self.to_dict()
|
||||||
|
"""La synthèse (dictionnaire) des notes/classements/statistiques"""
|
||||||
|
|
||||||
|
def comp_moy_et_stat(self, notes: pd.Series) -> dict:
|
||||||
|
"""Calcule et structure les données nécessaires au PE pour une série
|
||||||
|
de notes (pouvant être une moyenne d'un tag à une UE ou une moyenne générale
|
||||||
|
d'un tag) dans un dictionnaire spécifique.
|
||||||
|
|
||||||
|
Partant des notes, sont calculés les classements (en ne tenant compte
|
||||||
|
que des notes non nulles).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
notes: Une série de notes (avec des éventuels NaN)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Un dictionnaire stockant les notes, les classements, le min,
|
||||||
|
le max, la moyenne, le nb de notes (donc d'inscrits)
|
||||||
|
"""
|
||||||
|
df = pd.DataFrame(
|
||||||
|
np.nan,
|
||||||
|
index=self.etudids,
|
||||||
|
columns=Moyenne.CRITERES,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Supprime d'éventuelles chaines de caractères dans les notes
|
||||||
|
notes = pd.to_numeric(notes, errors="coerce")
|
||||||
|
df["note"] = notes
|
||||||
|
|
||||||
|
# Les nb d'étudiants & nb d'inscrits
|
||||||
|
df["nb_etuds"] = len(self.etudids)
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Le rang (classement/nb_inscrit)
|
||||||
|
df["rang"] = df["rang"].astype(str)
|
||||||
|
df.loc[inscrits_ids, "rang"] = (
|
||||||
|
df.loc[inscrits_ids, "classement"].astype(int).astype(str)
|
||||||
|
+ "/"
|
||||||
|
+ df.loc[inscrits_ids, "nb_inscrits"].astype(int).astype(str)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Les stat (des inscrits)
|
||||||
|
df.loc[inscrits_ids, "min"] = notes.min()
|
||||||
|
df.loc[inscrits_ids, "max"] = notes.max()
|
||||||
|
df.loc[inscrits_ids, "moy"] = notes.mean()
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""Renvoie un dictionnaire de synthèse des moyennes/classements/statistiques générale (but)"""
|
||||||
|
synthese = {
|
||||||
|
"notes": self.df["note"],
|
||||||
|
"classements": self.df["classement"],
|
||||||
|
"min": self.df["min"].mean(),
|
||||||
|
"max": self.df["max"].mean(),
|
||||||
|
"moy": self.df["moy"].mean(),
|
||||||
|
"nb_inscrits": self.df["nb_inscrits"].mean(),
|
||||||
|
}
|
||||||
|
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"]
|
||||||
|
|
||||||
|
def get_min(self) -> pd.Series:
|
||||||
|
"""Série des min"""
|
||||||
|
return self.df_gen["min"].round(2)
|
||||||
|
|
||||||
|
def get_max(self) -> pd.Series:
|
||||||
|
"""Série des max"""
|
||||||
|
return self.df_gen["max"].round(2)
|
||||||
|
|
||||||
|
def get_moy(self) -> pd.Series:
|
||||||
|
"""Série des moy"""
|
||||||
|
return self.df_gen["moy"].round(2)
|
||||||
|
|
||||||
|
def get_note_for_df(self, etudid: int):
|
||||||
|
"""Note d'un étudiant donné par son etudid"""
|
||||||
|
return round(self.df_gen["note"].loc[etudid], 2)
|
||||||
|
|
||||||
|
def get_min_for_df(self) -> float:
|
||||||
|
"""Min renseigné pour affichage dans un df"""
|
||||||
|
return round(self.synthese["min"], 2)
|
||||||
|
|
||||||
|
def get_max_for_df(self) -> float:
|
||||||
|
"""Max renseigné pour affichage dans un df"""
|
||||||
|
return round(self.synthese["max"], 2)
|
||||||
|
|
||||||
|
def get_moy_for_df(self) -> float:
|
||||||
|
"""Moyenne renseignée pour affichage dans un df"""
|
||||||
|
return round(self.synthese["moy"], 2)
|
||||||
|
|
||||||
|
def get_class_for_df(self, etudid: int) -> str:
|
||||||
|
"""Classement ramené au nombre d'inscrits,
|
||||||
|
pour un étudiant donné par son etudid"""
|
||||||
|
classement = self.df_gen["rang"].loc[etudid]
|
||||||
|
if not pd.isna(classement):
|
||||||
|
return classement
|
||||||
|
else:
|
||||||
|
return pe_affichage.SANS_NOTE
|
||||||
|
|
||||||
|
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,
|
||||||
|
ues: list[UniteEns],
|
||||||
|
notes_ues: pd.DataFrame,
|
||||||
|
# notes_gen: pd.Series,
|
||||||
|
):
|
||||||
|
"""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
|
||||||
|
ues: La liste des UEs ayant servie au calcul de la moyenne
|
||||||
|
notes_ues: Les moyennes (etudid x acronymes_ues) aux différentes UEs et pour le tag
|
||||||
|
# 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 UE
|
||||||
|
self.ues: dict[int, UniteEns] = {ue.id: ue for ue in ues}
|
||||||
|
"""Les UEs sur lesquelles sont calculées les moyennes"""
|
||||||
|
colonnes = list(notes_ues.columns)
|
||||||
|
acronymes = [self.ues[ue_id].acronyme for ue_id in colonnes]
|
||||||
|
assert len(set(acronymes)) == len(colonnes), \
|
||||||
|
"Deux UEs ne peuvent pas avoir le même acronyme"
|
||||||
|
|
||||||
|
# Les moyennes par UE
|
||||||
|
self.notes_ues = notes_ues
|
||||||
|
"""Les notes aux UEs (dataframe)"""
|
||||||
|
self.notes_ues.columns = acronymes # remplace les ue.id par leur acronyme
|
||||||
|
self.moys_ues: dict[int, pd.DataFrame] = {}
|
||||||
|
"""Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs"""
|
||||||
|
for ue in self.ues.values(): # if ue.type != UE_SPORT:
|
||||||
|
notes = notes_ues[ue.acronyme]
|
||||||
|
self.moys_ues[ue.acronyme] = Moyenne(notes)
|
||||||
|
|
||||||
|
# Les moyennes générales
|
||||||
|
self.notes_gen = notes_gen
|
||||||
|
"""Les notes générales (moyenne toutes UEs confonudes)"""
|
||||||
|
self.moy_gen = Moyenne(notes_gen)
|
||||||
|
"""Le dataframe retraçant les moyennes/classements/statistiques général"""
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""Egalité de deux MoyenneTag lorsque leur tag sont identiques"""
|
||||||
|
return self.tag == other.tag
|
@ -116,6 +116,10 @@ class RCS:
|
|||||||
self.semestres_aggreges = {}
|
self.semestres_aggreges = {}
|
||||||
"""Semestres regroupés dans le RCS"""
|
"""Semestres regroupés dans le RCS"""
|
||||||
|
|
||||||
|
def get_formsemestre_id_final(self):
|
||||||
|
"""Renvoie l'identifiant du formsemestre final du RCS"""
|
||||||
|
return self.formsemestre_final.formsemestre_id
|
||||||
|
|
||||||
def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]):
|
def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]):
|
||||||
"""Ajout de semestres aux semestres à regrouper
|
"""Ajout de semestres aux semestres à regrouper
|
||||||
|
|
||||||
|
@ -37,17 +37,19 @@ Created on Fri Sep 9 09:15:05 2016
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from app.comp.res_sem import load_formsemestre_results
|
from app.comp.res_sem import load_formsemestre_results
|
||||||
from app.pe.pe_semtag import SemestreTag
|
from app.pe import pe_affichage
|
||||||
|
from app.pe.pe_ressemtag import ResSemTag
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from app.pe.pe_rcs import RCS
|
from app.pe.pe_rcs import RCS
|
||||||
|
|
||||||
from app.pe.pe_tabletags import TableTag, MoyenneTag
|
from app.pe.pe_tabletags import TableTag
|
||||||
|
from app.pe.pe_moytag import MoyennesTag
|
||||||
|
|
||||||
|
|
||||||
class RCSTag(TableTag):
|
class RCSTag(TableTag):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, rcs: RCS, semestres_taggues: dict[int, SemestreTag]
|
self, rcs: RCS, semestres_taggues: dict[int, ResSemTag]
|
||||||
):
|
):
|
||||||
"""Calcule les moyennes par tag d'une combinaison de semestres
|
"""Calcule les moyennes par tag d'une combinaison de semestres
|
||||||
(RCS), pour extraire les classements par tag pour un
|
(RCS), pour extraire les classements par tag pour un
|
||||||
@ -104,11 +106,11 @@ class RCSTag(TableTag):
|
|||||||
self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted)
|
self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted)
|
||||||
"""Calcul les moyennes par tag sous forme d'un dataframe"""
|
"""Calcul les moyennes par tag sous forme d'un dataframe"""
|
||||||
|
|
||||||
self.moyennes_tags: dict[str, MoyenneTag] = {}
|
self.moyennes_tags: dict[str, 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:
|
||||||
moy_gen_tag = self.notes[tag]
|
moy_gen_tag = self.notes[tag]
|
||||||
self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)
|
self.moyennes_tags[tag] = MoyennesTag(tag, moy_gen_tag)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
|
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
|
||||||
@ -172,6 +174,7 @@ class RCSTag(TableTag):
|
|||||||
tags = []
|
tags = []
|
||||||
for frmsem_id in self.semestres_tags_aggreges:
|
for frmsem_id in self.semestres_tags_aggreges:
|
||||||
tags.extend(self.semestres_tags_aggreges[frmsem_id].tags_sorted)
|
tags.extend(self.semestres_tags_aggreges[frmsem_id].tags_sorted)
|
||||||
|
pe_affichage.pe_print(f"* Tags : {', '.join(tags)}")
|
||||||
return sorted(set(tags))
|
return sorted(set(tags))
|
||||||
|
|
||||||
|
|
||||||
|
373
app/pe/pe_ressemtag.py
Normal file
373
app/pe/pe_ressemtag.py
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
# -*- 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 Fri Sep 9 09:15:05 2016
|
||||||
|
|
||||||
|
@author: barasc
|
||||||
|
"""
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
import app.pe.pe_etudiant
|
||||||
|
from app import db, ScoValueError
|
||||||
|
from app import comp
|
||||||
|
from app.comp.res_sem import load_formsemestre_results
|
||||||
|
from app.models import FormSemestre
|
||||||
|
from app.models.moduleimpls import ModuleImpl
|
||||||
|
import app.pe.pe_affichage as pe_affichage
|
||||||
|
import app.pe.pe_etudiant as pe_etudiant
|
||||||
|
from app.pe.pe_tabletags import TableTag
|
||||||
|
from app.pe.pe_moytag import MoyennesTag
|
||||||
|
from app.scodoc import sco_tag_module
|
||||||
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
|
|
||||||
|
|
||||||
|
class ResSemTag(TableTag):
|
||||||
|
"""
|
||||||
|
Un ResSemTag représente les résultats des étudiants à un semestre, en donnant
|
||||||
|
accès aux moyennes par tag.
|
||||||
|
Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, formsemestre_id: int):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base
|
||||||
|
"""
|
||||||
|
TableTag.__init__(self)
|
||||||
|
|
||||||
|
# Le semestre
|
||||||
|
self.formsemestre_id = formsemestre_id
|
||||||
|
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
|
|
||||||
|
# Le nom du res_semestre taggué
|
||||||
|
self.nom = self.get_repr(mode="long")
|
||||||
|
|
||||||
|
pe_affichage.pe_print(
|
||||||
|
f"--> Résultats de Semestre taggués {self.nom}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Les résultats du semestre
|
||||||
|
self.nt = load_formsemestre_results(self.formsemestre)
|
||||||
|
|
||||||
|
# Les étudiants
|
||||||
|
self.etuds = self.nt.etuds
|
||||||
|
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
|
||||||
|
self.etudids = list(self.etudiants.keys())
|
||||||
|
|
||||||
|
# Les notes, les modules implémentés triés, les étudiants, les coeffs,
|
||||||
|
# récupérés notamment de py:mod:`res_but`
|
||||||
|
self.sem_cube = self.nt.sem_cube
|
||||||
|
self.modimpls_sorted = self.nt.formsemestre.modimpls_sorted
|
||||||
|
self.modimpl_coefs_df = self.nt.modimpl_coefs_df
|
||||||
|
|
||||||
|
# Les inscriptions aux modules
|
||||||
|
self.modimpl_inscr_df = self.nt.modimpl_inscr_df
|
||||||
|
|
||||||
|
# Les UEs (et les dispenses d'UE)
|
||||||
|
self.ues = self.nt.ues
|
||||||
|
ues_hors_sport = [ue for ue in self.ues if ue.type != UE_SPORT]
|
||||||
|
self.ues_inscr_parcours_df = self.nt.load_ues_inscr_parcours()
|
||||||
|
self.dispense_ues = self.nt.dispense_ues
|
||||||
|
|
||||||
|
# Les tags personnalisés et auto:
|
||||||
|
tags_dict = self._get_tags_dict()
|
||||||
|
self._check_tags(tags_dict)
|
||||||
|
# self.tags = [tag for cat in dict_tags for tag in dict_tags[cat]]
|
||||||
|
|
||||||
|
# Calcul des moyennes & les classements de chaque étudiant à chaque tag
|
||||||
|
self.moyennes_tags = {}
|
||||||
|
|
||||||
|
for tag in tags_dict["personnalises"]:
|
||||||
|
# pe_affichage.pe_print(f" -> Traitement du tag {tag}")
|
||||||
|
infos_tag = tags_dict["personnalises"][tag]
|
||||||
|
moy_ues_tag = self.compute_moy_ues_tag(infos_tag)
|
||||||
|
# moy_gen_tag = self.compute_moy_gen_tag(moy_ues_tag)
|
||||||
|
|
||||||
|
self.moyennes_tags[tag] = MoyennesTag(
|
||||||
|
tag, ues_hors_sport, moy_ues_tag # moy_gen_tag
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ajoute les d'UE moyennes générales de BUT pour le semestre considéré
|
||||||
|
# moy_gen_but = self.nt.etud_moy_gen
|
||||||
|
# self.moyennes_tags["but"] = MoyenneTag("but", [], None, moy_gen_but, )
|
||||||
|
|
||||||
|
# Ajoute les moyennes par UEs (et donc par compétence) + la moyenne générale (but)
|
||||||
|
df_ues = pd.DataFrame(
|
||||||
|
{ue.id: self.nt.etud_moy_ue[ue.id] for ue in ues_hors_sport},
|
||||||
|
index=self.etudids,
|
||||||
|
)
|
||||||
|
# moy_ues = self.nt.etud_moy_ue[ue_id]
|
||||||
|
# moy_gen_but = self.nt.etud_moy_gen
|
||||||
|
self.moyennes_tags["but"] = MoyennesTag(
|
||||||
|
"but", ues_hors_sport, df_ues #, moy_gen_but
|
||||||
|
)
|
||||||
|
|
||||||
|
self.tags_sorted = self.get_all_tags()
|
||||||
|
"""Tags (personnalisés+compétences) par ordre alphabétique"""
|
||||||
|
|
||||||
|
# Synthétise l'ensemble des moyennes dans un dataframe
|
||||||
|
|
||||||
|
# self.notes = self.df_notes()
|
||||||
|
# """Dataframe synthétique des notes par tag"""
|
||||||
|
|
||||||
|
# pe_affichage.pe_print(
|
||||||
|
# f" => Traitement des tags {', '.join(self.tags_sorted)}"
|
||||||
|
# )
|
||||||
|
|
||||||
|
def get_repr(self, mode="long"):
|
||||||
|
"""Nom affiché pour le semestre taggué"""
|
||||||
|
if mode == "short":
|
||||||
|
return f"{self.formsemestre} ({self.formsemestre_id})"
|
||||||
|
else: # mode == "long"
|
||||||
|
return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
|
||||||
|
|
||||||
|
def compute_moy_ues_tag(self, info_tag: dict[int, dict]) -> pd.DataFrame:
|
||||||
|
"""Calcule la moyenne par UE des étudiants pour un tag,
|
||||||
|
en ayant connaissance des informations sur le tag.
|
||||||
|
|
||||||
|
Les informations sur le tag sont un dictionnaire listant les modimpl_id rattachés au tag,
|
||||||
|
et pour chacun leur éventuel coefficient de **repondération**.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Le dataframe des moyennes du tag par UE
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Adaptation du mask de calcul des moyennes au tag visé
|
||||||
|
modimpls_mask = [
|
||||||
|
modimpl.module.ue.type != UE_SPORT
|
||||||
|
for modimpl in self.formsemestre.modimpls_sorted
|
||||||
|
]
|
||||||
|
|
||||||
|
# Désactive tous les modules qui ne sont pas pris en compte pour ce tag
|
||||||
|
for i, modimpl in enumerate(self.formsemestre.modimpls_sorted):
|
||||||
|
if modimpl.moduleimpl_id not in info_tag:
|
||||||
|
modimpls_mask[i] = False
|
||||||
|
|
||||||
|
# Applique la pondération des coefficients
|
||||||
|
modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy()
|
||||||
|
for modimpl_id in info_tag:
|
||||||
|
ponderation = info_tag[modimpl_id]["ponderation"]
|
||||||
|
modimpl_coefs_ponderes_df[modimpl_id] *= ponderation
|
||||||
|
|
||||||
|
# Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)
|
||||||
|
moyennes_ues_tag = comp.moy_ue.compute_ue_moys_apc(
|
||||||
|
self.sem_cube,
|
||||||
|
self.etuds,
|
||||||
|
self.formsemestre.modimpls_sorted,
|
||||||
|
self.modimpl_inscr_df,
|
||||||
|
modimpl_coefs_ponderes_df,
|
||||||
|
modimpls_mask,
|
||||||
|
self.dispense_ues,
|
||||||
|
block=self.formsemestre.block_moyennes,
|
||||||
|
)
|
||||||
|
return moyennes_ues_tag
|
||||||
|
|
||||||
|
def compute_moy_gen_tag(self, moy_ues_tag: pd.DataFrame) -> pd.Series:
|
||||||
|
"""Calcule la moyenne générale (toutes UE confondus)
|
||||||
|
pour le tag considéré, en les pondérant par les crédits ECTS.
|
||||||
|
"""
|
||||||
|
# Les ects
|
||||||
|
ects = self.ues_inscr_parcours_df.fillna(0.0) * [
|
||||||
|
ue.ects for ue in self.ues if ue.type != UE_SPORT
|
||||||
|
]
|
||||||
|
|
||||||
|
# Calcule la moyenne générale dans le semestre (pondérée par le ECTS)
|
||||||
|
moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects(
|
||||||
|
moy_ues_tag,
|
||||||
|
ects,
|
||||||
|
formation_id=self.formsemestre.formation_id,
|
||||||
|
skip_empty_ues=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return moy_gen_tag
|
||||||
|
|
||||||
|
def _get_tags_dict(self):
|
||||||
|
"""Renvoie les tags personnalisés (déduits des modules du semestre)
|
||||||
|
et les tags automatiques ('but'), et toutes leurs informations,
|
||||||
|
dans un dictionnaire de la forme :
|
||||||
|
|
||||||
|
``{"personnalises": {tag: info_sur_le_tag},
|
||||||
|
"auto": {tag: {}}``
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Le dictionnaire structuré des tags ("personnalises" vs. "auto")
|
||||||
|
"""
|
||||||
|
dict_tags = {"personnalises": dict(), "auto": dict()}
|
||||||
|
# Les tags perso
|
||||||
|
dict_tags["personnalises"] = get_synthese_tags_personnalises_semestre(
|
||||||
|
self.nt.formsemestre
|
||||||
|
)
|
||||||
|
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
|
||||||
|
pe_affichage.pe_print(
|
||||||
|
f"* Tags personnalisés (extraits du programme de formation) : {', '.join(noms_tags_perso)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Les tags automatiques
|
||||||
|
# Déduit des compétences
|
||||||
|
# dict_ues_competences = get_noms_competences_from_ues(self.nt.formsemestre)
|
||||||
|
# noms_tags_comp = list(set(dict_ues_competences.values()))
|
||||||
|
|
||||||
|
# BUT
|
||||||
|
dict_tags["auto"] = {"but": {}}
|
||||||
|
|
||||||
|
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
|
||||||
|
pe_affichage.pe_print(
|
||||||
|
f"* Tags automatiquement ajoutés : {', '.join(noms_tags_auto)}"
|
||||||
|
)
|
||||||
|
return dict_tags
|
||||||
|
|
||||||
|
def _check_tags(self, dict_tags):
|
||||||
|
"""Vérifie l'unicité des tags"""
|
||||||
|
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
|
||||||
|
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
|
||||||
|
noms_tags = noms_tags_perso + noms_tags_auto
|
||||||
|
|
||||||
|
intersection = list(set(noms_tags_perso) & set(noms_tags_auto))
|
||||||
|
|
||||||
|
if intersection:
|
||||||
|
liste_intersection = "\n".join(
|
||||||
|
[f"<li><code>{tag}</code></li>" for tag in intersection]
|
||||||
|
)
|
||||||
|
s = "s" if len(intersection) > 1 else ""
|
||||||
|
message = f"""Erreur dans le module PE : Un des tags saisis dans votre
|
||||||
|
programme de formation fait parti des tags réservés. En particulier,
|
||||||
|
votre semestre <em>{self.formsemestre.titre_annee()}</em>
|
||||||
|
contient le{s} tag{s} réservé{s} suivant :
|
||||||
|
<ul>
|
||||||
|
{liste_intersection}
|
||||||
|
</ul>
|
||||||
|
Modifiez votre programme de formation pour le{s} supprimer.
|
||||||
|
Il{s} ser{'ont' if s else 'a'} automatiquement à vos documents de poursuites d'études.
|
||||||
|
"""
|
||||||
|
raise ScoValueError(message)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_moduleimpl(modimpl_id) -> dict:
|
||||||
|
"""Renvoie l'objet modimpl dont l'id est modimpl_id"""
|
||||||
|
modimpl = db.session.get(ModuleImpl, modimpl_id)
|
||||||
|
if modimpl:
|
||||||
|
return modimpl
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float:
|
||||||
|
"""Renvoie la moyenne de l'UE d'un etudid dans laquelle se trouve
|
||||||
|
le module de modimpl_id
|
||||||
|
"""
|
||||||
|
# ré-écrit
|
||||||
|
modimpl = get_moduleimpl(modimpl_id) # le module
|
||||||
|
ue_status = nt.get_etud_ue_status(etudid, modimpl.module.ue.id)
|
||||||
|
if ue_status is None:
|
||||||
|
return None
|
||||||
|
return ue_status["moy"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
|
||||||
|
"""Etant données les implémentations des modules du semestre (modimpls),
|
||||||
|
synthétise les tags renseignés dans le programme pédagogique &
|
||||||
|
associés aux modules du semestre,
|
||||||
|
en les associant aux modimpls qui les concernent (modimpl_id) et
|
||||||
|
au coeff de repondération fournie avec le tag (par défaut 1 si non indiquée)).
|
||||||
|
|
||||||
|
Le dictionnaire fournit est de la forme :
|
||||||
|
|
||||||
|
``{ tag : { modimplid: {"modimpl": ModImpl,
|
||||||
|
"ponderation": coeff_de_reponderation}
|
||||||
|
} }``
|
||||||
|
|
||||||
|
Args:
|
||||||
|
formsemestre: Le formsemestre à la base de la recherche des tags
|
||||||
|
|
||||||
|
Return:
|
||||||
|
Un dictionnaire décrivant les tags
|
||||||
|
"""
|
||||||
|
synthese_tags = {}
|
||||||
|
|
||||||
|
# Instance des modules du semestre
|
||||||
|
modimpls = formsemestre.modimpls_sorted
|
||||||
|
|
||||||
|
for modimpl in modimpls:
|
||||||
|
modimpl_id = modimpl.id
|
||||||
|
|
||||||
|
# Liste des tags pour le module concerné
|
||||||
|
tags = sco_tag_module.module_tag_list(modimpl.module.id)
|
||||||
|
|
||||||
|
# Traitement des tags recensés, chacun pouvant étant de la forme
|
||||||
|
# "mathématiques", "théorie", "pe:0", "maths:2"
|
||||||
|
for tag in tags:
|
||||||
|
# Extraction du nom du tag et du coeff de pondération
|
||||||
|
(tagname, ponderation) = sco_tag_module.split_tagname_coeff(tag)
|
||||||
|
|
||||||
|
# Ajout d'une clé pour le tag
|
||||||
|
if tagname not in synthese_tags:
|
||||||
|
synthese_tags[tagname] = {}
|
||||||
|
|
||||||
|
# Ajout du module (modimpl) au tagname considéré
|
||||||
|
synthese_tags[tagname][modimpl_id] = {
|
||||||
|
"modimpl": modimpl, # les données sur le module
|
||||||
|
# "coeff": modimpl.module.coefficient, # le coeff du module dans le semestre
|
||||||
|
"ponderation": ponderation, # la pondération demandée pour le tag sur le module
|
||||||
|
# "module_code": modimpl.module.code, # le code qui doit se retrouver à l'identique dans des ue capitalisee
|
||||||
|
# "ue_id": modimpl.module.ue.id, # les données sur l'ue
|
||||||
|
# "ue_code": modimpl.module.ue.ue_code,
|
||||||
|
# "ue_acronyme": modimpl.module.ue.acronyme,
|
||||||
|
}
|
||||||
|
|
||||||
|
return synthese_tags
|
||||||
|
|
||||||
|
|
||||||
|
def get_noms_competences_from_ues(formsemestre: FormSemestre) -> dict[int, str]:
|
||||||
|
"""Partant d'un formsemestre, extrait le nom des compétences associés
|
||||||
|
à (ou aux) parcours des étudiants du formsemestre.
|
||||||
|
|
||||||
|
Ignore les UEs non associées à un niveau de compétence.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
formsemestre: Un FormSemestre
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionnaire {ue_id: nom_competence} lisant tous les noms des compétences
|
||||||
|
en les raccrochant à leur ue
|
||||||
|
"""
|
||||||
|
# Les résultats du semestre
|
||||||
|
nt = load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
|
noms_competences = {}
|
||||||
|
for ue in nt.ues:
|
||||||
|
if ue.niveau_competence and ue.type != UE_SPORT:
|
||||||
|
# ?? inutilisé ordre = ue.niveau_competence.ordre
|
||||||
|
nom = ue.niveau_competence.competence.titre
|
||||||
|
noms_competences[ue.ue_id] = f"comp. {nom}"
|
||||||
|
return noms_competences
|
@ -35,290 +35,216 @@ Created on Fri Sep 9 09:15:05 2016
|
|||||||
|
|
||||||
@author: barasc
|
@author: barasc
|
||||||
"""
|
"""
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
import app.pe.pe_etudiant
|
|
||||||
from app import db, ScoValueError
|
|
||||||
from app import comp
|
|
||||||
from app.comp.res_sem import load_formsemestre_results
|
from app.comp.res_sem import load_formsemestre_results
|
||||||
from app.models import FormSemestre
|
from app.models import UniteEns
|
||||||
from app.models.moduleimpls import ModuleImpl
|
from app.pe import pe_affichage
|
||||||
import app.pe.pe_affichage as pe_affichage
|
from app.pe.pe_ressemtag import ResSemTag
|
||||||
from app.pe.pe_tabletags import TableTag, MoyenneTag
|
import pandas as pd
|
||||||
from app.scodoc import sco_tag_module
|
import numpy as np
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.pe.pe_rcs import RCS
|
||||||
|
|
||||||
|
from app.pe.pe_tabletags import TableTag
|
||||||
|
from app.pe.pe_moytag import MoyennesTag
|
||||||
|
|
||||||
|
|
||||||
class SemestreTag(TableTag):
|
class SemTag(TableTag):
|
||||||
"""
|
def __init__(self, rcs: RCS, semestres_taggues: dict[int, ResSemTag]):
|
||||||
Un SemestreTag représente les résultats des étudiants à un semestre, en donnant
|
"""Calcule les moyennes/classements par tag à un RCS d'un seul semestre
|
||||||
accès aux moyennes par tag.
|
(ici semestre) de type 'Sx' (par ex. 'S1', 'S2', ...) :
|
||||||
Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT.
|
|
||||||
"""
|
* pour les étudiants non redoublants, ce sont les moyennes/classements
|
||||||
|
du semestre suivi
|
||||||
|
* pour les étudiants redoublants, c'est une fusion des moyennes/classements
|
||||||
|
suivis les différents 'Sx' (donné par dans le rcs)
|
||||||
|
|
||||||
|
Les **tags considérés** sont uniquement ceux du dernier semestre du RCS
|
||||||
|
|
||||||
def __init__(self, formsemestre_id: int):
|
|
||||||
"""
|
|
||||||
Args:
|
Args:
|
||||||
formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base
|
rcs: Un RCS (identifié par un nom et l'id de son semestre terminal)
|
||||||
|
semestres_taggues: Les données sur les semestres taggués
|
||||||
"""
|
"""
|
||||||
TableTag.__init__(self)
|
TableTag.__init__(self)
|
||||||
|
|
||||||
# Le semestre
|
self.rcs_id = rcs.rcs_id
|
||||||
self.formsemestre_id = formsemestre_id
|
"""Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)"""
|
||||||
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
||||||
|
|
||||||
# Le nom du semestre taggué
|
self.rcs = rcs
|
||||||
|
"""RCS associé au RCS taggué"""
|
||||||
|
|
||||||
|
assert self.rcs.nom.startswith(
|
||||||
|
"S"
|
||||||
|
), "Un SemTag ne peut être utilisé que pour un RCS de la forme Sx"
|
||||||
self.nom = self.get_repr()
|
self.nom = self.get_repr()
|
||||||
|
"""Représentation textuelle du RCS taggué"""
|
||||||
|
|
||||||
# Les résultats du semestre
|
# Les données du formsemestre_terminal
|
||||||
self.nt = load_formsemestre_results(self.formsemestre)
|
self.formsemestre_terminal = rcs.formsemestre_final
|
||||||
|
"""Le formsemestre terminal"""
|
||||||
|
|
||||||
# Les étudiants
|
# Les résultats du formsemestre terminal
|
||||||
self.etuds = self.nt.etuds
|
nt = load_formsemestre_results(self.formsemestre_terminal)
|
||||||
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
|
|
||||||
self.etudids = list(self.etudiants.keys())
|
|
||||||
|
|
||||||
# Les notes, les modules implémentés triés, les étudiants, les coeffs,
|
self.semestres_aggreges = rcs.semestres_aggreges
|
||||||
# récupérés notamment de py:mod:`res_but`
|
"""Les semestres aggrégés"""
|
||||||
self.sem_cube = self.nt.sem_cube
|
|
||||||
self.modimpls_sorted = self.nt.formsemestre.modimpls_sorted
|
|
||||||
self.modimpl_coefs_df = self.nt.modimpl_coefs_df
|
|
||||||
|
|
||||||
# Les inscriptions au module et les dispenses d'UE
|
self.semestres_tags_aggreges = {}
|
||||||
self.modimpl_inscr_df = self.nt.modimpl_inscr_df
|
"""Les semestres tags associés aux semestres aggrégés"""
|
||||||
self.ues = self.nt.ues
|
try:
|
||||||
self.ues_inscr_parcours_df = self.nt.load_ues_inscr_parcours()
|
for frmsem_id in self.semestres_aggreges:
|
||||||
self.dispense_ues = self.nt.dispense_ues
|
self.semestres_tags_aggreges[frmsem_id] = semestres_taggues[frmsem_id]
|
||||||
|
except:
|
||||||
|
raise ValueError("Semestres taggués manquants")
|
||||||
|
|
||||||
# Les tags :
|
# Les données des étudiants
|
||||||
## Saisis par l'utilisateur
|
self.etuds = nt.etuds
|
||||||
tags_personnalises = get_synthese_tags_personnalises_semestre(
|
"""Les étudiants"""
|
||||||
self.nt.formsemestre
|
self.etudids = [etud.etudid for etud in self.etuds]
|
||||||
)
|
"""Les etudids"""
|
||||||
noms_tags_perso = sorted(list(set(tags_personnalises.keys())))
|
self.etats_civils = {
|
||||||
|
etudid: self.etuds[etudid].etat_civil for etudid in self.etudids
|
||||||
|
}
|
||||||
|
"""Les états civils"""
|
||||||
|
|
||||||
## Déduit des compétences
|
# Les tags
|
||||||
# dict_ues_competences = get_noms_competences_from_ues(self.nt.formsemestre)
|
self.tags_sorted = self.comp_tags_list()
|
||||||
# noms_tags_comp = list(set(dict_ues_competences.values()))
|
"""Tags extraits du semestre terminal de l'aggrégat"""
|
||||||
noms_tags_auto = ["but"] # + noms_tags_comp
|
|
||||||
self.tags = noms_tags_perso + noms_tags_auto
|
|
||||||
"""Tags du semestre taggué"""
|
|
||||||
|
|
||||||
## Vérifie l'unicité des tags
|
# Les UEs
|
||||||
if len(set(self.tags)) != len(self.tags):
|
self.ues = self.comp_ues(tag="but")
|
||||||
intersection = list(set(noms_tags_perso) & set(noms_tags_auto))
|
self.acronymes_ues_sorted = sorted([ue.acronyme for ue in self.ues.values()])
|
||||||
liste_intersection = "\n".join(
|
"""UEs extraites du semestre terminal de l'aggrégat (avec
|
||||||
[f"<li><code>{tag}</code></li>" for tag in intersection]
|
check de concordance sur les UE des semestres_aggrégés)"""
|
||||||
)
|
|
||||||
s = "s" if len(intersection) > 0 else ""
|
|
||||||
message = f"""Erreur dans le module PE : Un des tags saisis dans votre
|
|
||||||
programme de formation fait parti des tags réservés. En particulier,
|
|
||||||
votre semestre <em>{self.formsemestre.titre_annee()}</em>
|
|
||||||
contient le{s} tag{s} réservé{s} suivant :
|
|
||||||
<ul>
|
|
||||||
{liste_intersection}
|
|
||||||
</ul>
|
|
||||||
Modifiez votre programme de formation pour le{s} supprimer.
|
|
||||||
Il{s} ser{'ont' if s else 'a'} automatiquement à vos documents de poursuites d'études.
|
|
||||||
"""
|
|
||||||
raise ScoValueError(message)
|
|
||||||
|
|
||||||
ues_hors_sport = [ue for ue in self.ues if ue.type != UE_SPORT]
|
self.moyennes_tags: dict[str, MoyennesTag] = {}
|
||||||
|
"""Moyennes/classements par tag (qu'ils soient personnalisés ou automatiques)"""
|
||||||
|
|
||||||
# Calcul des moyennes & les classements de chaque étudiant à chaque tag
|
self.notes: dict[str, pd.DataFrame] = {}
|
||||||
self.moyennes_tags = {}
|
"""Les notes aux différents tags"""
|
||||||
|
for tag in self.tags_sorted:
|
||||||
|
# Cube de note
|
||||||
|
notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues)
|
||||||
|
|
||||||
for tag in tags_personnalises:
|
# Calcule des moyennes sous forme d'un dataframe"""
|
||||||
# pe_affichage.pe_print(f" -> Traitement du tag {tag}")
|
self.notes[tag] = compute_notes_ues(notes_cube, self.etudids, self.acronymes_ues)
|
||||||
moy_ues_tag = self.compute_moy_ues_tag(tag, tags_personnalises)
|
|
||||||
moy_gen_tag = self.compute_moy_gen_tag(moy_ues_tag)
|
|
||||||
|
|
||||||
self.moyennes_tags[tag] = MoyenneTag(
|
# Les moyennes
|
||||||
tag, ues_hors_sport, moy_ues_tag, moy_gen_tag
|
self.moyennes_tags[tag] = MoyennesTag(tag, self.notes[tag])
|
||||||
)
|
|
||||||
|
|
||||||
# Ajoute les d'UE moyennes générales de BUT pour le semestre considéré
|
def __eq__(self, other):
|
||||||
# moy_gen_but = self.nt.etud_moy_gen
|
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
|
||||||
# self.moyennes_tags["but"] = MoyenneTag("but", [], None, moy_gen_but, )
|
return self.rcs_id == other.rcs_id
|
||||||
|
|
||||||
# Ajoute les moyennes par UEs (et donc par compétence) + la moyenne générale (but)
|
def get_repr(self, verbose=False) -> str:
|
||||||
df_ues = pd.DataFrame({ue.id: self.nt.etud_moy_ue[ue.id] for ue in ues_hors_sport},
|
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
|
||||||
index = self.etudids)
|
est basée)"""
|
||||||
# moy_ues = self.nt.etud_moy_ue[ue_id]
|
return self.rcs.get_repr(verbose=verbose)
|
||||||
moy_gen_but = self.nt.etud_moy_gen
|
|
||||||
self.moyennes_tags["but"] = MoyenneTag("but", ues_hors_sport, df_ues, moy_gen_but)
|
|
||||||
|
|
||||||
|
def compute_notes_ues_cube(self, tag, acronymes_ues_sorted):
|
||||||
|
"""Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé)
|
||||||
|
nécessaire au calcul des moyennes du tag pour le RCS Sx
|
||||||
|
"""
|
||||||
|
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
|
||||||
|
etudids = [etud.etudid for etud in self.etuds]
|
||||||
|
# acronymes_ues = sorted([ue.acronyme for ue in self.ues.values()])
|
||||||
|
semestres_id = list(self.semestres_tags_aggreges.keys())
|
||||||
|
|
||||||
self.tags_sorted = self.get_all_tags()
|
dfs = {}
|
||||||
"""Tags (personnalisés+compétences) par ordre alphabétique"""
|
|
||||||
|
|
||||||
# Synthétise l'ensemble des moyennes dans un dataframe
|
for frmsem_id in semestres_id:
|
||||||
|
# Partant d'un dataframe vierge
|
||||||
|
df = pd.DataFrame(np.nan, index=etudids, columns=acronymes_ues_sorted)
|
||||||
|
|
||||||
self.notes = self.df_notes()
|
# Charge les notes du semestre tag
|
||||||
"""Dataframe synthétique des notes par tag"""
|
sem_tag = self.semestres_tags_aggreges[frmsem_id]
|
||||||
|
moys_tag = sem_tag.moyennes_tags[tag]
|
||||||
|
notes = moys_tag.notes_ues # dataframe etudids x ues
|
||||||
|
acronymes_ues_sem = list(notes.columns) # les acronymes des UEs du semestre tag
|
||||||
|
|
||||||
pe_affichage.pe_print(
|
# UEs communes à celles du SemTag (celles du dernier semestre du RCS)
|
||||||
f" => Traitement des tags {', '.join(self.tags_sorted)}"
|
ues_communes = list(set(acronymes_ues_sorted) & set(acronymes_ues_sem))
|
||||||
)
|
|
||||||
|
|
||||||
def get_repr(self):
|
# Etudiants communs
|
||||||
"""Nom affiché pour le semestre taggué"""
|
etudids_communs = df.index.intersection(notes.index)
|
||||||
return app.pe.pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
|
|
||||||
|
|
||||||
def compute_moy_ues_tag(self, tag: str, tags_infos: dict) -> pd.Series:
|
# Recopie
|
||||||
"""Calcule la moyenne par UE des étudiants pour le tag indiqué,
|
df.loc[etudids_communs, ues_communes] = notes.loc[etudids_communs, ues_communes]
|
||||||
pour ce SemestreTag, en ayant connaissance des informations sur
|
|
||||||
les tags (dictionnaire donnant les coeff de repondération)
|
|
||||||
|
|
||||||
Sont pris en compte les modules implémentés associés au tag,
|
# Supprime tout ce qui n'est pas numérique
|
||||||
avec leur éventuel coefficient de **repondération**, en utilisant les notes
|
for col in df.columns:
|
||||||
chargées pour ce SemestreTag.
|
df[col] = pd.to_numeric(df[col], errors="coerce")
|
||||||
|
|
||||||
Force ou non le calcul de la moyenne lorsque des notes sont manquantes.
|
# Stocke le df
|
||||||
|
dfs[frmsem_id] = df
|
||||||
|
|
||||||
|
"""Réunit les notes sous forme d'un cube semestres x etdids x ues"""
|
||||||
|
semestres_x_etudids_x_ues = [dfs[fid].values for fid in dfs]
|
||||||
|
etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1)
|
||||||
|
return etudids_x_ues_x_semestres
|
||||||
|
|
||||||
|
def comp_tags_list(self):
|
||||||
|
"""Récupère les tag du semestre taggué associé au semestre final du RCS
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
La série des moyennes
|
Une liste de tags triés par ordre alphabétique
|
||||||
"""
|
"""
|
||||||
|
tags = []
|
||||||
|
dernier_frmid = self.formsemestre_terminal.formsemestre_id
|
||||||
|
dernier_semestre_tag = self.semestres_tags_aggreges[dernier_frmid]
|
||||||
|
tags = dernier_semestre_tag.tags_sorted
|
||||||
|
pe_affichage.pe_print(f"* Tags : {', '.join(tags)}")
|
||||||
|
return tags
|
||||||
|
|
||||||
# Adaptation du mask de calcul des moyennes au tag visé
|
def comp_ues(self, tag="but") -> dict[int, UniteEns]:
|
||||||
modimpls_mask = [
|
"""Récupère les UEs à aggréger, en s'appuyant sur la moyenne générale
|
||||||
modimpl.module.ue.type != UE_SPORT
|
(tag but) du semestre final du RCS
|
||||||
for modimpl in self.formsemestre.modimpls_sorted
|
|
||||||
]
|
|
||||||
|
|
||||||
# Désactive tous les modules qui ne sont pas pris en compte pour ce tag
|
Returns:
|
||||||
for i, modimpl in enumerate(self.formsemestre.modimpls_sorted):
|
Un dictionnaire donnant les UEs
|
||||||
if modimpl.moduleimpl_id not in tags_infos[tag]:
|
|
||||||
modimpls_mask[i] = False
|
|
||||||
|
|
||||||
# Applique la pondération des coefficients
|
|
||||||
modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy()
|
|
||||||
for modimpl_id in tags_infos[tag]:
|
|
||||||
ponderation = tags_infos[tag][modimpl_id]["ponderation"]
|
|
||||||
modimpl_coefs_ponderes_df[modimpl_id] *= ponderation
|
|
||||||
|
|
||||||
# Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)#
|
|
||||||
moyennes_ues_tag = comp.moy_ue.compute_ue_moys_apc(
|
|
||||||
self.sem_cube,
|
|
||||||
self.etuds,
|
|
||||||
self.formsemestre.modimpls_sorted,
|
|
||||||
self.modimpl_inscr_df,
|
|
||||||
modimpl_coefs_ponderes_df,
|
|
||||||
modimpls_mask,
|
|
||||||
self.dispense_ues,
|
|
||||||
block=self.formsemestre.block_moyennes,
|
|
||||||
)
|
|
||||||
return moyennes_ues_tag
|
|
||||||
|
|
||||||
def compute_moy_gen_tag(self, moy_ues_tag: pd.DataFrame) -> pd.Series:
|
|
||||||
"""Calcule la moyenne générale (toutes UE confondus)
|
|
||||||
pour le tag considéré, en les pondérant par les crédits ECTS.
|
|
||||||
"""
|
"""
|
||||||
# Les ects
|
dernier_frmid = self.formsemestre_terminal.formsemestre_id
|
||||||
ects = self.ues_inscr_parcours_df.fillna(0.0) * [
|
dernier_semestre_tag = self.semestres_tags_aggreges[dernier_frmid]
|
||||||
ue.ects for ue in self.ues if ue.type != UE_SPORT
|
moy_tag = dernier_semestre_tag.moyennes_tags[tag]
|
||||||
]
|
return moy_tag.ues # les UEs
|
||||||
|
|
||||||
# Calcule la moyenne générale dans le semestre (pondérée par le ECTS)
|
|
||||||
moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects(
|
|
||||||
moy_ues_tag,
|
|
||||||
ects,
|
|
||||||
formation_id=self.formsemestre.formation_id,
|
|
||||||
skip_empty_ues=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return moy_gen_tag
|
|
||||||
|
|
||||||
|
|
||||||
def get_moduleimpl(modimpl_id) -> dict:
|
def compute_notes_ues(set_cube: np.array, etudids: list, acronymes_ues: list):
|
||||||
"""Renvoie l'objet modimpl dont l'id est modimpl_id"""
|
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
|
||||||
modimpl = db.session.get(ModuleImpl, modimpl_id)
|
par UE) obtenue par un étudiant à un semestre.
|
||||||
if modimpl:
|
|
||||||
return modimpl
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float:
|
|
||||||
"""Renvoie la moyenne de l'UE d'un etudid dans laquelle se trouve
|
|
||||||
le module de modimpl_id
|
|
||||||
"""
|
|
||||||
# ré-écrit
|
|
||||||
modimpl = get_moduleimpl(modimpl_id) # le module
|
|
||||||
ue_status = nt.get_etud_ue_status(etudid, modimpl.module.ue.id)
|
|
||||||
if ue_status is None:
|
|
||||||
return None
|
|
||||||
return ue_status["moy"]
|
|
||||||
|
|
||||||
|
|
||||||
def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
|
|
||||||
"""Etant données les implémentations des modules du semestre (modimpls),
|
|
||||||
synthétise les tags renseignés dans le programme pédagogique &
|
|
||||||
associés aux modules du semestre,
|
|
||||||
en les associant aux modimpls qui les concernent (modimpl_id) et
|
|
||||||
aucoeff et pondération fournie avec le tag (par défaut 1 si non indiquée)).
|
|
||||||
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
formsemestre: Le formsemestre à la base de la recherche des tags
|
set_cube: notes moyennes aux modules ndarray
|
||||||
|
(semestre_ids x etudids x UEs), des floats avec des NaN
|
||||||
Return:
|
etudids: liste des étudiants (dim. 0 du cube)
|
||||||
Un dictionnaire de tags
|
acronymes_ues: liste des acronymes des ues (dim. 1 du cube)
|
||||||
"""
|
|
||||||
synthese_tags = {}
|
|
||||||
|
|
||||||
# Instance des modules du semestre
|
|
||||||
modimpls = formsemestre.modimpls_sorted
|
|
||||||
|
|
||||||
for modimpl in modimpls:
|
|
||||||
modimpl_id = modimpl.id
|
|
||||||
|
|
||||||
# Liste des tags pour le module concerné
|
|
||||||
tags = sco_tag_module.module_tag_list(modimpl.module.id)
|
|
||||||
|
|
||||||
# Traitement des tags recensés, chacun pouvant étant de la forme
|
|
||||||
# "mathématiques", "théorie", "pe:0", "maths:2"
|
|
||||||
for tag in tags:
|
|
||||||
# Extraction du nom du tag et du coeff de pondération
|
|
||||||
(tagname, ponderation) = sco_tag_module.split_tagname_coeff(tag)
|
|
||||||
|
|
||||||
# Ajout d'une clé pour le tag
|
|
||||||
if tagname not in synthese_tags:
|
|
||||||
synthese_tags[tagname] = {}
|
|
||||||
|
|
||||||
# Ajout du module (modimpl) au tagname considéré
|
|
||||||
synthese_tags[tagname][modimpl_id] = {
|
|
||||||
"modimpl": modimpl, # les données sur le module
|
|
||||||
# "coeff": modimpl.module.coefficient, # le coeff du module dans le semestre
|
|
||||||
"ponderation": ponderation, # la pondération demandée pour le tag sur le module
|
|
||||||
# "module_code": modimpl.module.code, # le code qui doit se retrouver à l'identique dans des ue capitalisee
|
|
||||||
# "ue_id": modimpl.module.ue.id, # les données sur l'ue
|
|
||||||
# "ue_code": modimpl.module.ue.ue_code,
|
|
||||||
# "ue_acronyme": modimpl.module.ue.acronyme,
|
|
||||||
}
|
|
||||||
|
|
||||||
return synthese_tags
|
|
||||||
|
|
||||||
|
|
||||||
def get_noms_competences_from_ues(formsemestre: FormSemestre) -> dict[int, str]:
|
|
||||||
"""Partant d'un formsemestre, extrait le nom des compétences associés
|
|
||||||
à (ou aux) parcours des étudiants du formsemestre.
|
|
||||||
|
|
||||||
Ignore les UEs non associées à un niveau de compétence.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
formsemestre: Un FormSemestre
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionnaire {ue_id: nom_competence} lisant tous les noms des compétences
|
Un DataFrame avec pour columns les moyennes par ues,
|
||||||
en les raccrochant à leur ue
|
et pour rows les etudid
|
||||||
"""
|
"""
|
||||||
# Les résultats du semestre
|
nb_etuds, nb_ues, nb_semestres = set_cube.shape
|
||||||
nt = load_formsemestre_results(formsemestre)
|
assert nb_etuds == len(etudids)
|
||||||
|
assert nb_ues == len(acronymes_ues)
|
||||||
|
|
||||||
noms_competences = {}
|
# Quelles entrées du cube contiennent des notes ?
|
||||||
for ue in nt.ues:
|
mask = ~np.isnan(set_cube)
|
||||||
if ue.niveau_competence and ue.type != UE_SPORT:
|
|
||||||
# ?? inutilisé ordre = ue.niveau_competence.ordre
|
# Enlève les NaN du cube pour les entrées manquantes
|
||||||
nom = ue.niveau_competence.competence.titre
|
set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0)
|
||||||
noms_competences[ue.ue_id] = f"comp. {nom}"
|
|
||||||
return noms_competences
|
# Les moyennes par ues
|
||||||
|
# TODO: Pour l'instant un max sans prise en compte des UE capitalisées
|
||||||
|
etud_moy = np.max(set_cube_no_nan, axis=2)
|
||||||
|
|
||||||
|
# Fix les max non calculé -1 -> NaN
|
||||||
|
etud_moy[etud_moy < 0] = np.NaN
|
||||||
|
|
||||||
|
# Le dataFrame
|
||||||
|
etud_moy_tag_df = pd.DataFrame(
|
||||||
|
etud_moy,
|
||||||
|
index=etudids, # les etudids
|
||||||
|
columns=acronymes_ues, # les tags
|
||||||
|
)
|
||||||
|
|
||||||
|
etud_moy_tag_df.fillna(np.nan)
|
||||||
|
|
||||||
|
return etud_moy_tag_df
|
||||||
|
@ -37,195 +37,11 @@ Created on Thu Sep 8 09:36:33 2016
|
|||||||
@author: barasc
|
@author: barasc
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from app import ScoValueError
|
|
||||||
from app.comp.moy_sem import comp_ranks_series
|
|
||||||
from app.models import UniteEns
|
|
||||||
from app.pe import pe_affichage
|
|
||||||
from app.pe.pe_affichage import SANS_NOTE
|
|
||||||
from app.scodoc import sco_utils as scu
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
|
||||||
|
|
||||||
TAGS_RESERVES = ["but"]
|
TAGS_RESERVES = ["but"]
|
||||||
|
|
||||||
|
|
||||||
class MoyenneTag:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
tag: str,
|
|
||||||
ues: list[UniteEns],
|
|
||||||
notes_ues: pd.DataFrame,
|
|
||||||
notes_gen: pd.Series,
|
|
||||||
):
|
|
||||||
"""Classe centralisant la synthèse des moyennes/classements d'une série
|
|
||||||
d'étudiants à un tag donné, en stockant :
|
|
||||||
|
|
||||||
``
|
|
||||||
{
|
|
||||||
"notes": la Serie pandas des notes (float),
|
|
||||||
"classements": la Serie pandas des classements (float),
|
|
||||||
"min": la note minimum,
|
|
||||||
"max": la note maximum,
|
|
||||||
"moy": la moyenne,
|
|
||||||
"nb_inscrits": le nombre d'étudiants ayant une note,
|
|
||||||
}
|
|
||||||
``
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tag: Un tag
|
|
||||||
ues: La liste des UEs ayant servie au calcul de la moyenne
|
|
||||||
notes_ues: Les moyennes (etudid x ues) aux différentes UEs et pour le tag
|
|
||||||
notes_gen: Une série de notes (moyenne) sous forme d'un pd.Series() (toutes UEs confondues)
|
|
||||||
"""
|
|
||||||
self.tag = tag
|
|
||||||
"""Le tag associé à la moyenne"""
|
|
||||||
self.etudids = list(notes_gen.index) # calcul à venir
|
|
||||||
"""Les id des étudiants"""
|
|
||||||
self.ues: list[UniteEns] = ues
|
|
||||||
"""Les UEs sur lesquelles sont calculées les moyennes"""
|
|
||||||
self.df_ues: dict[int, pd.DataFrame] = {}
|
|
||||||
"""Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs"""
|
|
||||||
for ue in self.ues: # if ue.type != UE_SPORT:
|
|
||||||
notes = notes_ues[ue.id]
|
|
||||||
self.df_ues[ue.id] = self.comp_moy_et_stat(notes)
|
|
||||||
|
|
||||||
self.inscrits_ids = notes_gen[notes_gen.notnull()].index.to_list()
|
|
||||||
"""Les id des étudiants dont la moyenne générale est non nulle"""
|
|
||||||
self.df_gen: pd.DataFrame = self.comp_moy_et_stat(notes_gen)
|
|
||||||
"""Le dataframe retraçant les moyennes/classements/statistiques général"""
|
|
||||||
self.synthese = self.to_dict()
|
|
||||||
"""La synthèse (dictionnaire) des notes/classements/statistiques"""
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
"""Egalité de deux MoyenneTag lorsque leur tag sont identiques"""
|
|
||||||
return self.tag == other.tag
|
|
||||||
|
|
||||||
def comp_moy_et_stat(self, notes: pd.Series) -> dict:
|
|
||||||
"""Calcule et structure les données nécessaires au PE pour une série
|
|
||||||
de notes (pouvant être une moyenne d'un tag à une UE ou une moyenne générale
|
|
||||||
d'un tag) dans un dictionnaire spécifique.
|
|
||||||
|
|
||||||
Partant des notes, sont calculés les classements (en ne tenant compte
|
|
||||||
que des notes non nulles).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
notes: Une série de notes (avec des éventuels NaN)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Un dictionnaire stockant les notes, les classements, le min,
|
|
||||||
le max, la moyenne, le nb de notes (donc d'inscrits)
|
|
||||||
"""
|
|
||||||
df = pd.DataFrame(
|
|
||||||
np.nan,
|
|
||||||
index=self.etudids,
|
|
||||||
columns=[
|
|
||||||
"note",
|
|
||||||
"classement",
|
|
||||||
"rang",
|
|
||||||
"min",
|
|
||||||
"max",
|
|
||||||
"moy",
|
|
||||||
"nb_etuds",
|
|
||||||
"nb_inscrits",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Supprime d'éventuelles chaines de caractères dans les notes
|
|
||||||
notes = pd.to_numeric(notes, errors="coerce")
|
|
||||||
df["note"] = notes
|
|
||||||
|
|
||||||
# Les nb d'étudiants & nb d'inscrits
|
|
||||||
df["nb_etuds"] = len(self.etudids)
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Le rang (classement/nb_inscrit)
|
|
||||||
df["rang"] = df["rang"].astype(str)
|
|
||||||
df.loc[inscrits_ids, "rang"] = (
|
|
||||||
df.loc[inscrits_ids, "classement"].astype(int).astype(str)
|
|
||||||
+ "/"
|
|
||||||
+ df.loc[inscrits_ids, "nb_inscrits"].astype(int).astype(str)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Les stat (des inscrits)
|
|
||||||
df.loc[inscrits_ids, "min"] = notes.min()
|
|
||||||
df.loc[inscrits_ids, "max"] = notes.max()
|
|
||||||
df.loc[inscrits_ids, "moy"] = notes.mean()
|
|
||||||
|
|
||||||
return df
|
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
"""Renvoie un dictionnaire de synthèse des moyennes/classements/statistiques"""
|
|
||||||
synthese = {
|
|
||||||
"notes": self.df_gen["note"],
|
|
||||||
"classements": self.df_gen["classement"],
|
|
||||||
"min": self.df_gen["min"].mean(),
|
|
||||||
"max": self.df_gen["max"].mean(),
|
|
||||||
"moy": self.df_gen["moy"].mean(),
|
|
||||||
"nb_inscrits": self.df_gen["nb_inscrits"].mean(),
|
|
||||||
}
|
|
||||||
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"]
|
|
||||||
|
|
||||||
def get_min(self) -> pd.Series:
|
|
||||||
"""Série des min"""
|
|
||||||
return self.df_gen["min"].round(2)
|
|
||||||
|
|
||||||
def get_max(self) -> pd.Series:
|
|
||||||
"""Série des max"""
|
|
||||||
return self.df_gen["max"].round(2)
|
|
||||||
|
|
||||||
def get_moy(self) -> pd.Series:
|
|
||||||
"""Série des moy"""
|
|
||||||
return self.df_gen["moy"].round(2)
|
|
||||||
|
|
||||||
def get_note_for_df(self, etudid: int):
|
|
||||||
"""Note d'un étudiant donné par son etudid"""
|
|
||||||
return round(self.df_gen["note"].loc[etudid], 2)
|
|
||||||
|
|
||||||
def get_min_for_df(self) -> float:
|
|
||||||
"""Min renseigné pour affichage dans un df"""
|
|
||||||
return round(self.synthese["min"], 2)
|
|
||||||
|
|
||||||
def get_max_for_df(self) -> float:
|
|
||||||
"""Max renseigné pour affichage dans un df"""
|
|
||||||
return round(self.synthese["max"], 2)
|
|
||||||
|
|
||||||
def get_moy_for_df(self) -> float:
|
|
||||||
"""Moyenne renseignée pour affichage dans un df"""
|
|
||||||
return round(self.synthese["moy"], 2)
|
|
||||||
|
|
||||||
def get_class_for_df(self, etudid: int) -> str:
|
|
||||||
"""Classement ramené au nombre d'inscrits,
|
|
||||||
pour un étudiant donné par son etudid"""
|
|
||||||
classement = self.df_gen["rang"].loc[etudid]
|
|
||||||
if not pd.isna(classement):
|
|
||||||
return classement
|
|
||||||
else:
|
|
||||||
return pe_affichage.SANS_NOTE
|
|
||||||
|
|
||||||
def is_significatif(self) -> bool:
|
|
||||||
"""Indique si la moyenne est significative (c'est-à-dire à des notes)"""
|
|
||||||
return self.synthese["nb_inscrits"] > 0
|
|
||||||
|
|
||||||
|
|
||||||
class TableTag(object):
|
class TableTag(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Classe centralisant différentes méthodes communes aux
|
"""Classe centralisant différentes méthodes communes aux
|
||||||
@ -233,7 +49,6 @@ class TableTag(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------
|
|
||||||
def get_all_tags(self):
|
def get_all_tags(self):
|
||||||
"""Liste des tags de la table, triée par ordre alphabétique,
|
"""Liste des tags de la table, triée par ordre alphabétique,
|
||||||
extraite des clés du dictionnaire ``moyennes_tags`` connues (tags en doublon
|
extraite des clés du dictionnaire ``moyennes_tags`` connues (tags en doublon
|
||||||
@ -261,8 +76,9 @@ class TableTag(object):
|
|||||||
tags_tries = self.get_all_tags()
|
tags_tries = self.get_all_tags()
|
||||||
for tag in tags_tries:
|
for tag in tags_tries:
|
||||||
moy_tag = self.moyennes_tags[tag]
|
moy_tag = self.moyennes_tags[tag]
|
||||||
df = df.join(moy_tag.synthese["notes"].rename(f"Moy {tag}"))
|
moy_gen = moy_tag.moy_gen
|
||||||
df = df.join(moy_tag.synthese["classements"].rename(f"Class {tag}"))
|
df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag}"))
|
||||||
|
df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag}"))
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ class DevConfig(Config):
|
|||||||
)
|
)
|
||||||
SECRET_KEY = os.environ.get("DEV_SECRET_KEY") or "bb3faec7d9a34eb68a8e3e710087d87a"
|
SECRET_KEY = os.environ.get("DEV_SECRET_KEY") or "bb3faec7d9a34eb68a8e3e710087d87a"
|
||||||
# pour le avoir url_for dans le shell:
|
# pour le avoir url_for dans le shell:
|
||||||
# SERVER_NAME = "http://localhost:8080"
|
SERVER_NAME = "http://localhost:8080"
|
||||||
|
|
||||||
|
|
||||||
class TestConfig(DevConfig):
|
class TestConfig(DevConfig):
|
||||||
|
Loading…
Reference in New Issue
Block a user