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
7 changed files with 259 additions and 307 deletions
Showing only changes of commit 828c619c74 - Show all commits

View File

@ -5,7 +5,7 @@
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
# Copyright (c) 1999 - 2024 Emmanuel Viennet. c 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
@ -59,7 +59,7 @@ class EtudiantsJuryPE:
self.identites: dict[int, Identite] = {} # ex. ETUDINFO_DICT
"Les identités des étudiants traités pour le jury"
self.cursus: dict[int, dict] = {}
self.trajectoires: dict[int, dict] = {}
"Les cursus (semestres suivis, abandons) des étudiants"
self.trajectoires = {}
@ -164,7 +164,7 @@ class EtudiantsJuryPE:
"""
etudids = [
etudid
for etudid, cursus_etud in self.cursus.items()
for etudid, cursus_etud in self.trajectoires.items()
if cursus_etud["diplome"] == self.annee_diplome
and cursus_etud["abandon"] is False
]
@ -181,7 +181,7 @@ class EtudiantsJuryPE:
"""
etudids = [
etudid
for etudid, cursus_etud in self.cursus.items()
for etudid, cursus_etud in self.trajectoires.items()
if cursus_etud["diplome"] != self.annee_diplome
or cursus_etud["abandon"] is True
]
@ -225,7 +225,7 @@ class EtudiantsJuryPE:
if formsemestre.formation.is_apc()
}
self.cursus[etudid] = {
self.trajectoires[etudid] = {
"etudid": etudid, # les infos sur l'étudiant
"etat_civil": identite.etat_civil, # Ajout à la table jury
"nom": identite.nom,
@ -241,32 +241,36 @@ class EtudiantsJuryPE:
}
# Si l'étudiant est succeptible d'être diplomé
if self.cursus[etudid]["diplome"] == self.annee_diplome:
if self.trajectoires[etudid]["diplome"] == self.annee_diplome:
# Est-il démissionnaire : charge son dernier semestre pour connaitre son état ?
dernier_semes_etudiant = formsemestres[0]
res = load_formsemestre_results(dernier_semes_etudiant)
etud_etat = res.get_etud_etat(etudid)
if etud_etat == scu.DEMISSION:
self.cursus[etudid]["abandon"] = True
self.trajectoires[etudid]["abandon"] = True
else:
# Est-il réorienté ou a-t-il arrêté (volontairement) sa formation ?
self.cursus[etudid]["abandon"] = arret_de_formation(
self.trajectoires[etudid]["abandon"] = arret_de_formation(
identite, cosemestres
)
def get_semestres_significatifs(self, etudid: int):
"""Ensemble des semestres d'un étudiant, qui l'auraient amené à être diplomé
l'année visée (supprime les semestres qui conduisent à une diplomation
postérieure à celle du jury visé)
"""Ensemble des semestres d'un étudiant, qui :
* l'amènent à être diplômé à l'année visée
* l'auraient amené à être diplômé à l'année visée s'il n'avait pas redoublé et sera donc
diplômé plus tard
Supprime les semestres qui conduisent à une diplomation postérieure à celle du jury visé.
Args:
etudid: L'identifiant d'un étudiant
Returns:
Un dictionnaire ``{fid: FormSemestre(fid)`` dans lequel les semestres
amènent à une diplomation avant l'annee de diplomation du jury
Un dictionnaire ``{fid: FormSemestre(fid)}`` dans lequel les semestres
amènent à une diplômation antérieur à celle de la diplômation visée par le jury jury
"""
semestres_etudiant = self.cursus[etudid]["formsemestres"]
semestres_etudiant = self.trajectoires[etudid]["formsemestres"]
semestres_significatifs = {}
for fid in semestres_etudiant:
semestre = semestres_etudiant[fid]
@ -281,7 +285,7 @@ class EtudiantsJuryPE:
Cette structuration s'appuie sur les numéros de semestre: pour chaque Si, stocke :
le dernier semestre (en date) de numéro i qu'il a suivi (1 ou 0 si pas encore suivi).
Ce semestre influera les interclassement par semestre dans la promo.
Ce semestre influera les interclassements par semestre dans la promo.
"""
semestres_significatifs = self.get_semestres_significatifs(etudid)
@ -293,14 +297,14 @@ class EtudiantsJuryPE:
for fid, sem_sig in semestres_significatifs.items()
if sem_sig.semestre_id == i
}
self.cursus[etudid][f"S{i}"] = semestres_i
self.trajectoires[etudid][f"S{i}"] = semestres_i
def get_formsemestres_terminaux_aggregat(
self, aggregat: str
def get_formsemestres_finals_des_rcs(
self, nom_rcs: str
) -> dict[int, FormSemestre]:
"""Pour un aggrégat donné, ensemble des formsemestres terminaux possibles pour l'aggrégat
(pour l'aggrégat '3S' incluant S1+S2+S3, a pour semestre terminal S3).
Ces formsemestres traduisent :
"""Pour un nom de RCS donné, ensemble des formsemestres finals possibles
pour les RCS. Par ex. un RCS '3S' incluant S1+S2+S3 a pour semestre final un S3.
Les formsemestres finals obtenus traduisent :
* les différents parcours des étudiants liés par exemple au choix de modalité
(par ex: S1 FI + S2 FI + S3 FI ou S1 FI + S2 FI + S3 UFA), en renvoyant les
@ -311,14 +315,14 @@ class EtudiantsJuryPE:
renvoyant les formsemestre_id du S3 (1ère session) et du S3 (2ème session)
Args:
aggregat: L'aggrégat
nom_rcs: Le nom du RCS (parmi Sx, xA, xS)
Returns:
Un dictionnaire ``{fid: FormSemestre(fid)}``
"""
formsemestres_terminaux = {}
for trajectoire_aggr in self.trajectoires.values():
trajectoire = trajectoire_aggr[aggregat]
trajectoire = trajectoire_aggr[nom_rcs]
if trajectoire:
# Le semestre terminal de l'étudiant de l'aggrégat
fid = trajectoire.formsemestre_final.formsemestre_id
@ -334,7 +338,7 @@ class EtudiantsJuryPE:
"""
nbres_semestres = []
for etudid in etudids:
nbres_semestres.append(self.cursus[etudid]["nb_semestres"])
nbres_semestres.append(self.trajectoires[etudid]["nb_semestres"])
if not nbres_semestres:
return 0
return max(nbres_semestres)
@ -355,7 +359,7 @@ class EtudiantsJuryPE:
for etudid in etudids:
etudiant = self.identites[etudid]
cursus = self.cursus[etudid]
cursus = self.trajectoires[etudid]
formsemestres = cursus["formsemestres"]
if cursus["diplome"]:
@ -442,7 +446,7 @@ def get_annee_diplome(etud: Identite) -> int | None:
def get_semestres_apc(identite: Identite) -> list:
"""Liste des semestres d'un étudiant qui corresponde à une formation APC.
"""Liste des semestres d'un étudiant qui correspondent à une formation APC.
Args:
identite: L'identité d'un étudiant
@ -675,3 +679,19 @@ def nom_semestre_etape(semestre: FormSemestre, avec_fid=False) -> str:
description.append(f"({semestre.formsemestre_id})")
return " ".join(description)
def convert_trajectoire_to_sxtag_id(trajectoire: dict[int:FormSemestre]) -> (int, int):
"""Partant d'une trajectoire (dictionnaire de la forme {fid: FormSemestre}),
renvoie l'identifiant (rang_sem, fid du semestre_terminal) associé"""
if not trajectoire:
return None
rangs = [formsemestre.semestre_id for formsemestre in trajectoire.values()]
assert rangs == min(rangs), "Les trajectoires doivent être de même rang"
rang = min(rangs)
fid_terminal = list(trajectoire.values())[0].formsemestre_id
for fid, formsemestre in trajectoire.items():
if trajectoire[fid_terminal].date_fin <= formsemestre.date_fin:
fid_terminal = fid
return (rang, fid_terminal)

View File

@ -50,13 +50,13 @@ from zipfile import ZipFile
import numpy as np
import pandas as pd
from app.pe import pe_semtag
from app.pe import pe_sxtag
from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE
import app.pe.pe_affichage as pe_affichage
from app.pe.pe_etudiant import * # TODO A éviter -> pe_etudiant.
import app.pe.pe_rcs as pe_rcs
from app.pe.pe_rcstag import RCSTag
from app.pe.pe_ressemtag import ResSemTag
from app.pe.pe_ressemtag import ResSemBUTTag
from app.pe.pe_interclasstag import RCSInterclasseTag
@ -97,10 +97,10 @@ class JuryPE(object):
pe_affichage.pe_print("*** Aucun étudiant diplômé")
else:
self._gen_xls_diplomes(zipfile)
self._gen_rcss()
self._gen_xls_resultats_semestres_taggues(zipfile)
self._gen_xls_semestres_taggues(zipfile)
# self._gen_xls_rcss_tags(zipfile)
self._gen_xls_ressembuttags(zipfile)
self._gen_xls_sxtags(zipfile)
self._gen_xls_rcss_tags(zipfile)
# self._gen_xls_interclassements_rcss(zipfile)
# self._gen_xls_synthese_jury_par_tag(zipfile)
# self._gen_xls_synthese_par_etudiant(zipfile)
@ -134,17 +134,26 @@ class JuryPE(object):
path="details",
)
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"""
pe_affichage.pe_print("*** Génère les résultats des semestres taggués")
self.res_sems_tags = compute_resultats_semestres_tag(self.etudiants)
def _gen_xls_ressembuttags(self, zipfile: ZipFile):
"""Calcule les moyennes par tag des résultats des Semestres BUT"""
pe_affichage.pe_print("*** Génère les ResSemBUTTag (résultats des semestres BUT taggués)")
formsemestres = get_formsemestres_etudiants(self.etudiants)
pe_affichage.pe_print(f" --> {len(formsemestres)} résultats de semestres à considérer")
ressembuttags = {}
for frmsem_id, formsemestre in formsemestres.items():
# Crée le semestre_tag et exécute les calculs de moyennes
ressembuttags[frmsem_id] = ResSemBUTTag(formsemestre)
return ressembuttags
# Intègre le bilan des semestres taggués au zip final
output = io.BytesIO()
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
for res_sem_tag in self.res_sems_tags.values():
for res_sem_tag in self.ressembuttags.values():
onglet = res_sem_tag.get_repr(verbose=False)
df = res_sem_tag.df_moyennes_et_classements()
# écriture dans l'onglet
@ -153,14 +162,14 @@ class JuryPE(object):
self.add_file_to_zip(
zipfile,
f"resultats_semestres_taggues_{self.diplome}.xlsx",
f"ResSemBUTTags_{self.diplome}.xlsx",
output.read(),
path="details",
)
def _gen_rcss(self):
"""Génère les RCS (attribut `rcss_jury`), combinaisons de semestres suivis par les étudiants au sens
d'un nom de RCS (par ex: '3S').
d'un nom de RCS (par ex: 'S2' ou '3S').
"""
pe_affichage.pe_print(
"*** Génère les RCS (différentes combinaisons de semestres) des étudiants"
@ -168,24 +177,35 @@ class JuryPE(object):
self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome)
self.rcss_jury.cree_rcss(self.etudiants)
def _gen_xls_semestres_taggues(self, zipfile: ZipFile):
def _gen_xls_sxtags(self, zipfile: ZipFile):
"""Génère les semestres taggués en s'appuyant sur les RCS de type Sx (pour
identifier les redoublements impactant les semestres taggués).
"""
# Génère les moyennes des RCS de type Sx
pe_affichage.pe_print("*** Calcule les moyennes de semestres = RCS de type Sx")
pe_affichage.pe_print("*** Calcule les moyennes des SxTag")
self.sems_tags = {}
for rcs_id, rcs in self.rcss_jury.rcss.items():
if rcs.nom.startswith("S"):
self.sems_tags[rcs_id] = pe_semtag.SemTag(rcs, self.res_sems_tags)
# Les regroupements de Sx
self.regroupements = {}
for rang in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT+1):
self.regroupements[rang] = {}
for etudid in self.etudiants.etudiants_ids:
trajectoire = self.etudiants.cursus[etudid][f"S{rang}"]
self.regroupements[rang] |= trajectoire
# Les SxTag
self.sxtags = {}
for rang in range(1, pe_comp.NBRE_SEMESTRES_DIPLOMANT+1):
trajectoire = self.regroupements[rang]
sxtag_id = pe_sxtag.get_sxtag_from_semestres(trajectoire)
ressemstags = {fid: self.ressembuttags[fid] for fid in trajectoire}
self.sxtags[sxtag_id] = pe_sxtag.SxTag(sxtag_id, ressemtags)
# Intègre le bilan des semestres taggués au zip final
output = io.BytesIO()
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
for sem_tag in self.sems_tags.values():
for sem_tag in self.sxtags.values():
onglet = sem_tag.get_repr(verbose=False)
df = sem_tag.df_moyennes_et_classements()
# écriture dans l'onglet
@ -200,11 +220,11 @@ class JuryPE(object):
)
def _gen_xls_rcss_tags(self, zipfile: ZipFile):
"""Génère les RCS tagguées des RCS, en calculant les moyennes et les classements par tag
pour chacune.
"""Génère les RCS taggués (autres que ceux de type Sx), etc...
en calculant les moyennes et les classements par tag pour chaque RCS.
Stocke le résultat dans self.rccs_tag, un dictionnaire de
la forme ``{nom_aggregat: {fid_terminal: SetTag(fid_terminal)} }``
la forme ``{nom_aggregat: {fid_terminal: RCSTag(fid_terminal)} }``
Pour rappel : Chaque RCS est identifié par un nom d'aggrégat et par un formsemestre terminal.
@ -213,19 +233,13 @@ class JuryPE(object):
* 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)
"""
# Génère les moyennes des RCS de type Sx
pe_affichage.pe_print("*** Calcule les moyennes des RCS de type Sx")
pe_affichage.pe_print("*** Calcule les moyennes des RCS")
self.rcss_tags = {}
for rcs_id, rcs in self.rcss_jury.rcss.items():
@ -409,7 +423,7 @@ class JuryPE(object):
for etudid in etudids:
trajectoire = self.rcss_jury.suivi[etudid][aggregat]
if trajectoire:
tid = trajectoire.rcs_id
tid = trajectoire.sxtag_id
trajectoire_tagguee = self.rcss_tags[tid]
if (
tag in trajectoire_tagguee.moyennes_tags
@ -555,7 +569,7 @@ class JuryPE(object):
# La trajectoire de l'étudiant sur l'aggrégat
trajectoire = self.rcss_jury.suivi[etudid][aggregat]
if trajectoire:
trajectoire_tagguee = self.rcss_tags[trajectoire.rcs_id]
trajectoire_tagguee = self.rcss_tags[trajectoire.sxtag_id]
if tag in trajectoire_tagguee.moyennes_tags:
# L'interclassement
interclass = self.interclassements_taggues[aggregat]
@ -581,9 +595,8 @@ class JuryPE(object):
def get_formsemestres_etudiants(etudiants: EtudiantsJuryPE) -> dict:
"""Ayant connaissance des étudiants dont il faut calculer les moyennes pour
le jury PE (attribut `self.etudiant_ids) et de leur cursus (semestres
parcourus),
renvoie un dictionnaire ``{fid: FormSemestre(fid)}``
le jury PE (attribut `self.etudiant_ids) et de leurs trajectoires (semestres
parcourus), renvoie un dictionnaire ``{fid: FormSemestre(fid)}``
contenant l'ensemble des formsemestres de leurs cursus, dont il faudra calculer
la moyenne.
@ -592,44 +605,17 @@ def get_formsemestres_etudiants(etudiants: EtudiantsJuryPE) -> dict:
Returns:
Un dictionnaire de la forme `{fid: FormSemestre(fid)}`
"""
semestres = {}
for etudid in etudiants.etudiants_ids:
for cle in etudiants.cursus[etudid]:
for cle in etudiants.trajectoires[etudid]:
if cle.startswith("S"):
semestres = semestres | etudiants.cursus[etudid][cle]
semestres = semestres | etudiants.trajectoires[etudid][cle]
return semestres
def compute_resultats_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
"""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
des étudiants (cf. attribut etudiants.cursus).
En crééant le semestre taggué, sont calculées les moyennes/classements par tag associé.
.
Args:
etudiants: Un groupe d'étudiants participant au jury
Returns:
Un dictionnaire {fid: SemestreTag(fid)}
"""
# Création des semestres taggués, de type 'S1', 'S2', ...
pe_affichage.pe_print("*** Création des semestres taggués")
formsemestres = get_formsemestres_etudiants(etudiants)
semestres_tags = {}
for frmsem_id, formsemestre in formsemestres.items():
# Crée le semestre_tag et exécute les calculs de moyennes
formsemestretag = ResSemTag(frmsem_id)
# Stocke le semestre taggué
semestres_tags[frmsem_id] = formsemestretag
return semestres_tags
def compute_interclassements(

View File

@ -12,9 +12,9 @@ Created on 01-2024
import app.pe.pe_comp as pe_comp
from app.models import FormSemestre
from app.pe import pe_sxtag
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
TYPES_RCS = {
"S1": {
"aggregat": ["S1"],
@ -81,11 +81,11 @@ TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")]
class RCS:
"""Modélise un ensemble de semestres d'étudiants
"""Modélise un ensemble de semestres finals d'étudiants
associé à un type de regroupement cohérent de semestres
donné (par ex: 'S2', '3S', '2A').
Si le RCS est un semestre de type Si, stocke le (ou les)
Si le RCS est un semestre de type Si, stocke le
formsemestres de numéro i qu'ont suivi l'étudiant pour atteindre le Si
(en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants)
@ -104,38 +104,40 @@ class RCS:
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
self.nom = nom_rcs
self.nom: str = nom_rcs
"""Nom du RCS"""
self.formsemestre_final = semestre_final
self.formsemestre_final: FormSemestre = semestre_final
"""FormSemestre terminal du RCS"""
self.rcs_id = (nom_rcs, semestre_final.formsemestre_id)
self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id)
"""Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)"""
self.semestres_aggreges = {}
"""Semestres regroupés dans le RCS"""
# self.semestres_aggreges: dict[int:FormSemestre] = {}
# """Semestres regroupés dans le RCS"""
self.sxtags_aggreges: dict[(str, int): pe_sxtag.SxTag] = {}
"""Les SxTag aggrégés"""
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]):
"""Ajout de semestres aux semestres à regrouper
def add_sxtags_a_aggreger(self, sxtags: dict[(str,int): pe_sxtag.SxTag]):
"""Ajout des SxTag aux semestres à regrouper
Args:
semestres: Dictionnaire ``{fid: FormSemestre(fid)}`` à ajouter
sxtags: Dictionnaire ``{(str,fid): SxTag}`` à ajouter
"""
self.semestres_aggreges = self.semestres_aggreges | semestres
self.sxtags_aggreges = self.sxtags_aggreges | sxtags
def get_repr(self, verbose=True) -> str:
"""Représentation textuelle d'un RCS
basé sur ses semestres aggrégés"""
basé sur ses sxtags aggrégés"""
noms = []
for fid in self.semestres_aggreges:
semestre = self.semestres_aggreges[fid]
noms.append(f"S{semestre.semestre_id}({fid})")
for sxtag_id, sxtag in self.sxtags_aggreges.items():
noms.append(f"S{sxtag.semestre_id}")
noms = sorted(noms)
title = f"""{self.nom} ({
self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"""
@ -145,7 +147,7 @@ class RCS:
class RCSsJuryPE:
"""Classe centralisant toutes les regroupements cohérents de
"""Classe centralisant tous les regroupements cohérents de
semestres (RCS) des étudiants à prendre en compte dans un jury PE
Args:
@ -163,21 +165,24 @@ class RCSsJuryPE:
"""Dictionnaire associant, pour chaque étudiant et pour chaque type de RCS,
son RCS : {etudid: {nom_RCS: RCS}}"""
def cree_rcss(self, etudiants: EtudiantsJuryPE):
def cree_rcss(self, etudiants: EtudiantsJuryPE, sxtags: dict[(str, int), pe_sxtag.SxTag]):
"""Créé tous les RCS, au regard du cursus des étudiants
analysés + les mémorise dans les données de l'étudiant
analysés et des SxTag calculés.
Les mémorise dans les données de chaque étudiant.
Args:
etudiants: Les étudiants à prendre en compte dans le Jury PE
pe_sxtag: Les Sx taggués
"""
for nom_rcs in pe_comp.TOUS_LES_SEMESTRES + TOUS_LES_RCS_AVEC_PLUSIEURS_SEM:
# L'aggrégat considéré (par ex: 3S=S1+S2+S3), son nom de son semestre
# terminal (par ex: S3) et son numéro (par ex: 3)
noms_semestre_de_aggregat = TYPES_RCS[nom_rcs]["aggregat"]
nom_semestre_terminal = noms_semestre_de_aggregat[-1]
noms_semestre_de_aggregat = TYPES_RCS[nom_rcs]["aggregat"] # ["S1", "S2", "S3"]
nom_semestre_terminal = noms_semestre_de_aggregat[-1] # "S3"
for etudid in etudiants.cursus:
for etudid in etudiants.trajectoires:
if etudid not in self.suivi:
self.suivi[etudid] = {
aggregat: None
@ -188,54 +193,57 @@ class RCSsJuryPE:
# Le formsemestre terminal (dernier en date) associé au
# semestre marquant la fin de l'aggrégat
# (par ex: son dernier S3 en date)
semestres = etudiants.cursus[etudid][nom_semestre_terminal]
semestres = etudiants.trajectoires[etudid][nom_semestre_terminal]
if semestres:
formsemestre_final = get_dernier_semestre_en_date(semestres)
# Ajout ou récupération de la trajectoire
trajectoire_id = (nom_rcs, formsemestre_final.formsemestre_id)
if trajectoire_id not in self.rcss:
trajectoire = RCS(nom_rcs, formsemestre_final)
self.rcss[trajectoire_id] = trajectoire
rcs_id = (nom_rcs, formsemestre_final.formsemestre_id)
if rcs_id not in self.rcss:
rcs = RCS(nom_rcs, formsemestre_final)
self.rcss[rcs_id] = rcs
else:
trajectoire = self.rcss[trajectoire_id]
rcs = self.rcss[rcs_id]
# La liste des semestres de l'étudiant à prendre en compte
# pour cette trajectoire
semestres_a_aggreger = get_rcs_etudiant(
etudiants.cursus[etudid], formsemestre_final, nom_rcs
semestres_a_aggreger = get_trajectoire_etudiant(
etudiants.trajectoires[etudid], formsemestre_final, nom_rcs
)
# Extrait les sxtags correspondants aux semestres à aggréger
# (par ex. des 2 semestres S1(18)+S1(26) récupère le sxtag S1(26)
sxtags_a_aggreger = {}
semestres_tries = pe_comp.tri_semestres_par_rang(semestres_a_aggreger)
for rang in semestres_tries:
sems = semestres_tries[rang] # les 1 ou 2 semestres de même rang suivi
sxtag_id = pe_sxtag.get_sxtag_from_semestres(sems, sxtags)
if not sxtag_id:
raise ValueError(f"Il manque un sxtag pour {sems}")
sxtags_a_aggreger[sxtag_id] = sxtags[sxtag_id]
# Ajout des semestres à la trajectoire
trajectoire.add_semestres_a_aggreger(semestres_a_aggreger)
rcs.add_sxtags_a_aggreger(sxtags_a_aggreger)
# Mémoire la trajectoire suivie par l'étudiant
self.suivi[etudid][nom_rcs] = trajectoire
self.suivi[etudid][nom_rcs] = rcs
def get_rcs_etudiant(
def get_trajectoire_etudiant(
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
) -> dict[int, FormSemestre]:
"""Ensemble des semestres parcourus par un étudiant, connaissant
les semestres de son cursus,
dans le cadre du RCS visé et ayant pour semestre terminal `formsemestre_final`.
"""Ensemble des semestres parcourus (trajectoire)
par un étudiant dans le cadre
d'un RCS de type Sx, iA ou iS et ayant pour semestre terminal `formsemestre_final`.
Si le RCS est de type "Si", limite les semestres à ceux de numéro i.
Par ex: si formsemestre_terminal est un S3 et nom_agrregat "S3", ne prend en compte que les
semestres 3.
Si le RCS est de type "iA" ou "iS" (incluant plusieurs numéros de semestres), prend en
compte les dit numéros de semestres.
Par ex: si formsemestre_terminal est un S3, ensemble des S1,
S2, S3 suivi pour l'amener au S3 (il peut y avoir plusieurs S1,
ou S2, ou S3 s'il a redoublé).
Par ex: pour un RCS "3S", dont le formsemestre_terminal est un S3, regroupe
le ou les S1 qu'il a suivi (1 ou 2 si redoublement) + le ou les S2 + le ou les S3.
Les semestres parcourus sont antérieurs (en terme de date de fin)
au formsemestre_terminal.
Args:
cursus: Dictionnaire {fid: FormSemestre(fid)} donnant l'ensemble des semestres
cursus: Dictionnaire {fid: Formsemestre} donnant l'ensemble des semestres
dans lesquels l'étudiant a été inscrit
formsemestre_final: le semestre final visé
nom_rcs: Nom du RCS visé

View File

@ -38,7 +38,7 @@ Created on Fri Sep 9 09:15:05 2016
from app.comp.res_sem import load_formsemestre_results
from app.pe import pe_affichage
from app.pe.pe_ressemtag import ResSemTag
from app.pe.pe_ressemtag import ResSemBUTTag
import pandas as pd
import numpy as np
from app.pe.pe_rcs import RCS
@ -48,26 +48,25 @@ from app.pe.pe_moytag import MoyennesTag
class RCSTag(TableTag):
def __init__(
self, rcs: RCS, semestres_taggues: dict[int, ResSemTag]
self, rcs: RCS, semestres_taggues: dict[int, ResSemBUTTag]
):
"""Calcule les moyennes par tag d'une combinaison de semestres
(RCS), pour extraire les classements par tag pour un
groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous
participé au semestre terminal.
Args:
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)
self.rcs_id = rcs.rcs_id
self.rcs_id: tuple(str, int) = rcs.rcs_id
"""Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)"""
self.rcs = rcs
self.rcs: RCS = rcs
"""RCS associé au RCS taggué"""
self.nom = self.get_repr()
@ -82,20 +81,21 @@ class RCSTag(TableTag):
self.semestres_aggreges = rcs.semestres_aggreges
"""Les semestres aggrégés"""
self.semestres_tags_aggreges = {}
self.res_sems_tags = {}
"""Les semestres tags associés aux semestres aggrégés"""
for frmsem_id in self.semestres_aggreges:
try:
self.semestres_tags_aggreges[frmsem_id] = semestres_taggues[frmsem_id]
for frmsem_id in self.semestres_aggreges:
self.res_sems_tags[frmsem_id] = semestres_taggues[frmsem_id]
except:
raise ValueError("Semestres taggués manquants")
"""Les étudiants (état civil + cursus connu)"""
self.etuds = nt.etuds
# Les étudiants (etuds, états civils & etudis)
self.add_etuds(nt.etuds)
# assert self.etuds == trajectoire.suivi # manque-t-il des étudiants ?
self.etats_civils = {etud.etudid: etud.nomprenom for etud in self.etuds}
# Les compétences (extraites des ues de tous les semestres)
self.ues = self.comp_ues(tag="but")
# Les tags
self.tags_sorted = self.do_taglist()
"""Tags extraits de tous les semestres"""
@ -114,7 +114,7 @@ class RCSTag(TableTag):
def __eq__(self, other):
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
return self.rcs_id == other.rcs_id
return self.rcs_id == other.sxtag_id
def get_repr(self, verbose=False) -> str:
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
@ -132,7 +132,7 @@ class RCSTag(TableTag):
# Index du cube (etudids -> dim 0, tags -> dim 1)
etudids = [etud.etudid for etud in self.etuds]
tags = self.tags_sorted
semestres_id = list(self.semestres_tags_aggreges.keys())
semestres_id = list(self.res_sems_tags.keys())
dfs = {}
@ -141,7 +141,7 @@ class RCSTag(TableTag):
df = pd.DataFrame(np.nan, index=etudids, columns=tags)
# Charge les notes du semestre tag
notes = self.semestres_tags_aggreges[frmsem_id].notes
notes = self.res_sems_tags[frmsem_id].notes
# Les étudiants & les tags commun au dataframe final et aux notes du semestre)
etudids_communs = df.index.intersection(notes.index)
@ -172,8 +172,8 @@ class RCSTag(TableTag):
Une liste de tags triés par ordre alphabétique
"""
tags = []
for frmsem_id in self.semestres_tags_aggreges:
tags.extend(self.semestres_tags_aggreges[frmsem_id].tags_sorted)
for frmsem_id in self.res_sems_tags:
tags.extend(self.res_sems_tags[frmsem_id].tags_sorted)
pe_affichage.pe_print(f"* Tags : {', '.join(tags)}")
return sorted(set(tags))

View File

@ -37,37 +37,38 @@ Created on Fri Sep 9 09:15:05 2016
"""
import pandas as pd
import app.pe.pe_etudiant
from app import db, 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
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
import app.pe.pe_tabletags as pe_tabletags
from app.pe.pe_moytag import MoyennesTag
from app.scodoc import sco_tag_module
from app.scodoc.codes_cursus import UE_SPORT
class ResSemTag(TableTag):
class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
"""
Un ResSemTag représente les résultats des étudiants à un semestre, en donnant
Un ResSemBUTTag 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):
def __init__(self, formsemestre: FormSemestre):
"""
Args:
formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base
formsemestre: le ``FormSemestre`` sur lequel il se base
"""
TableTag.__init__(self)
ResultatsSemestreBUT.__init__(self, formsemestre)
pe_tabletags.TableTag.__init__(self)
# Le semestre
self.formsemestre_id = formsemestre_id
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# self.formsemestre_id = self.formsemestre.formsemestre_id
# self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Le nom du res_semestre taggué
self.nom = self.get_repr(verbose=True)
@ -75,25 +76,14 @@ class ResSemTag(TableTag):
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)
# self.nt = load_formsemestre_results(self.formsemestre)
# Les étudiants (etuds, états civils & etudis)
self.add_etuds(self.nt.etuds)
# 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 étudiants (etuds, états civils & etudis) ajouté
self.add_etuds(self.etuds)
# 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
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
# Les tags personnalisés et auto:
tags_dict = self._get_tags_dict()
@ -124,7 +114,7 @@ class ResSemTag(TableTag):
# 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},
{ue.id: self.etud_moy_ue[ue.id] for ue in ues_hors_sport},
index=self.etudids,
)
# moy_ues = self.nt.etud_moy_ue[ue_id]
@ -150,7 +140,7 @@ class ResSemTag(TableTag):
def get_repr(self, verbose=False):
"""Nom affiché pour le semestre taggué"""
if verbose:
return f"{self.formsemestre} ({self.formsemestre_id})"
return f"{self.formsemestre} ({self.formsemestre.formsemestre_id})"
else:
return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
@ -209,7 +199,7 @@ class ResSemTag(TableTag):
dict_tags = {"personnalises": dict(), "auto": dict()}
# Les tags perso
dict_tags["personnalises"] = get_synthese_tags_personnalises_semestre(
self.nt.formsemestre
self.formsemestre
)
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
pe_affichage.pe_print(
@ -256,26 +246,6 @@ class ResSemTag(TableTag):
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 &

View File

@ -37,87 +37,79 @@ Created on Fri Sep 9 09:15:05 2016
"""
from app.comp.res_sem import load_formsemestre_results
from app.models import UniteEns
from app.models import UniteEns, FormSemestre
from app.pe import pe_affichage
from app.pe.pe_ressemtag import ResSemTag
from app.pe.pe_ressemtag import ResSemBUTTag
import pandas as pd
import numpy as np
from app.pe.pe_rcs import RCS
from app.pe.pe_tabletags import TableTag
from app.pe.pe_moytag import MoyennesTag
class SemTag(TableTag):
def __init__(self, rcs: RCS, res_sems_tags: dict[int, ResSemTag]):
"""Calcule les moyennes/classements par tag à un RCS d'un seul semestre
(ici semestre) de type 'Sx' (par ex. 'S1', 'S2', ...) :
class SxTag(TableTag):
def __init__(self, sxtag_id: (int, int), ressembuttags: dict[int, ResSemBUTTag]):
"""Calcule les moyennes/classements par tag d'un semestre de type 'Sx'
(par ex. 'S1', 'S2', ...) :
* 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)
dans les (2) 'Sx' qu'il a suivi
Les **tags considérés** sont uniquement ceux du dernier semestre du RCS
Un SxTag peut donc regrouper plusieurs semestres.
Un SxTag est identifié par un tuple (x, fid) x est le numéro (semestre_id)
du semestre et fid le formsemestre_id du semestre final (le plus récent) du
regrouprement.
Les **tags**, les **UE** et les inscriptions aux UEs (pour les etudiants)
considérés sont uniquement ceux du semestre final.
Args:
rcs: Un RCS (identifié par un nom et l'id de son semestre terminal)
res_sems_tags: Les données sur les résultats des semestres taggués
sxtag_id: L'identifiant de SxTag
ressembuttags: Un dictionnaire de la forme `{fid: ResSemBUTTag(fid)}` donnant
les semestres à regrouper et les résultats/moyennes par tag des
semestres
"""
TableTag.__init__(self)
self.rcs_id = rcs.rcs_id
"""Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)"""
assert sxtag_id and len(sxtag_id) == 2 and sxtag_id in ressembuttags
self.rcs = rcs
"""RCS associé au RCS taggué"""
self.sxtag_id: (int, int) = sxtag_id
"""Identifiant du SxTag de la forme (semestre_id, fid_semestre_final)"""
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()
"""Représentation textuelle du RCS taggué"""
self.ressembuttags = ressembuttags
"""Les ResSemBUTTags à regrouper dans le SxTag"""
# Les données du formsemestre_terminal
self.formsemestre_terminal = rcs.formsemestre_final
"""Le formsemestre terminal"""
# Les résultats du formsemestre terminal
nt = load_formsemestre_results(self.formsemestre_terminal)
self.semestres_aggreges = rcs.semestres_aggreges
"""Les semestres aggrégés"""
self.res_sems_tags = {}
"""Les résultats des semestres taggués (limités aux semestres aggrégés)"""
try:
for frmsem_id in self.semestres_aggreges:
self.res_sems_tags[frmsem_id] = res_sems_tags[frmsem_id]
except:
raise ValueError("Résultats des semestres taggués manquants")
# Les données du semestre final
self.fid_final = sxtag_id[1]
self.ressembuttag_final = ressembuttags[self.fid_final]
"""Le ResSemBUTTag final"""
# Les étudiants (etuds, états civils & etudis)
self.add_etuds(nt.etuds)
self.etuds = ressembuttags[self.fid_final].etuds
self.add_etuds(self.etuds)
# Les tags
self.tags_sorted = self.comp_tags_list()
"""Tags (extraits uniquement du semestre terminal de l'aggrégat)"""
self.tags_sorted = self.ressembuttag_final.tags_sorted
"""Tags (extraits uniquement du semestre final)"""
pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}")
# Les UEs
self.ues = self.comp_ues(tag="but")
# Les UE
self.ues = self.ressembuttag_final.moyennes_tags["but"].ues
# Les acronymes des UE
self.acronymes_ues_sorted = sorted([ue.acronyme for ue in self.ues.values()])
"""UEs extraites du semestre terminal de l'aggrégat (avec
check de concordance sur les UE des semestres_aggrégés)"""
# Les inscriptions aux UEs
self.ues_inscr_parcours_df = self.comp_ues_inscr_parcours(tag="but")
"""Les inscriptions aux UEs (extraites uniquement du semestre terminal)"""
self.moyennes_tags: dict[str, MoyennesTag] = {}
"""Moyennes/classements par tag (qu'ils soient personnalisés ou automatiques)"""
# Les inscriptions des étudiants aux UEs
# => ne conserve que les UEs du semestre final (pour les redoublants)
self.ues_inscr_parcours_df = self.ressembuttag_final.moyennes_tags["but"].ues_inscr_parcours_df
# Les moyennes par tag
self.moyennes_tags: dict[str, pd.DataFrame] = {}
"""Les notes aux UEs dans différents tags"""
# Masque des inscriptions
inscr_mask = self.ues_inscr_parcours_df.to_numpy()
for tag in self.tags_sorted:
@ -132,19 +124,19 @@ class SemTag(TableTag):
inscr_mask,
)
# Les moyennes
self.moyennes_tags[tag] = MoyennesTag(tag,
self.ues,
moys_ues,
self.ues_inscr_parcours_df)
self.moyennes_tags[tag] = MoyennesTag(
tag, self.ues, moys_ues, self.ues_inscr_parcours_df
)
def __eq__(self, other):
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
return self.rcs_id == other.rcs_id
"""Egalité de 2 SxTag sur la base de leur identifiant"""
return self.sxtag_id == other.sxtag_id
def get_repr(self, verbose=False) -> str:
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
est basée)"""
return self.rcs.get_repr(verbose=verbose)
affichage = str(fid) for fid in self.res
return f"S{sxtag_id[0]}Tag ({'+'.join()})"
def compute_notes_ues_cube(self, tag, acronymes_ues_sorted):
"""Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé)
@ -153,7 +145,7 @@ class SemTag(TableTag):
# 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.res_sems_tags.keys())
semestres_id = list(self.ressembuttags.keys())
dfs = {}
@ -162,7 +154,7 @@ class SemTag(TableTag):
df = pd.DataFrame(np.nan, index=etudids, columns=acronymes_ues_sorted)
# Charge les notes du semestre tag
sem_tag = self.res_sems_tags[frmsem_id]
sem_tag = self.ressembuttags[frmsem_id]
moys_tag = sem_tag.moyennes_tags[tag]
notes = moys_tag.notes_ues # dataframe etudids x ues
acronymes_ues_sem = list(
@ -192,43 +184,6 @@ class SemTag(TableTag):
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) -> list[str]:
"""Récupère les tag du semestre taggué associé au semestre final du RCS
Returns:
Une liste de tags triés par ordre alphabétique
"""
tags = []
dernier_frmid = self.formsemestre_terminal.formsemestre_id
dernier_semestre_tag = self.res_sems_tags[dernier_frmid]
tags = dernier_semestre_tag.tags_sorted
pe_affichage.pe_print(f"* Tags : {', '.join(tags)}")
return tags
def comp_ues(self, tag="but") -> dict[int, UniteEns]:
"""Récupère les UEs à aggréger, en s'appuyant sur la moyenne générale
(tag but) du semestre final du RCS
Returns:
Un dictionnaire donnant les UEs
"""
dernier_frmid = self.formsemestre_terminal.formsemestre_id
dernier_semestre_tag = self.res_sems_tags[dernier_frmid]
moy_tag = dernier_semestre_tag.moyennes_tags[tag]
return moy_tag.ues # les UEs
def comp_ues_inscr_parcours(self, tag="but") -> pd.DataFrame:
"""Récupère les informations d'inscription des étudiants aux UEs : ne
conserve que les UEs du semestre terminal (pour les redoublants)
Returns:
Un dataFrame etudids x UE indiquant si un étudiant est inscrit à une UE
"""
dernier_frmid = self.formsemestre_terminal.formsemestre_id
dernier_semestre_tag = self.res_sems_tags[dernier_frmid]
moy_tag = dernier_semestre_tag.moyennes_tags[tag]
return moy_tag.ues_inscr_parcours_df
def compute_notes_ues(
set_cube: np.array,
@ -284,3 +239,16 @@ def compute_notes_ues(
etud_moy_tag_df.fillna(np.nan)
return etud_moy_tag_df
def get_sxtag_from_semestres(
formsemestres: dict[int:FormSemestre], sxtags: dict[(str, int):SxTag]
) -> (str, int):
"""Partant d'un dictionnaire de SxTags, renvoie l'identifiant (str, fid) du
sxtag correspondant aux semestres de formsemestres.
(Utilisé pour transformer une trajectoire d'étudiants (par ex. S1+S2+S1+S2+S3)
en une suite de sxtags (S1+S2+S3)
"""
for sxtag_id, sxtag in sxtags:
if set(formsemestres.keys()) == set(sxtag.semestres_aggreges.keys()):
return sxtag_id

View File

@ -50,11 +50,11 @@ class TableTag(object):
SemestreTag, TrajectoireTag, AggregatInterclassTag
"""
# Les étudiants
self.etuds: list[Identite] = None # A venir
# self.etuds: list[Identite] = None # A venir
"""Les étudiants"""
self.etats_civils: dict[int, Identite] = None
# self.etats_civils: dict[int, Identite] = None
"""Les états civils"""
self.etudids: list[int] = None
# self.etudids: list[int] = None
"""Les etudids"""
def add_etuds(self, etuds: list[Identite]):
@ -63,7 +63,7 @@ class TableTag(object):
Args:
etuds: la liste des identités de l'étudiant
"""
self.etuds = etuds
# self.etuds = etuds
self.etats_civils = {etud.etudid: etud.etat_civil for etud in self.etuds}
self.etudids = list(self.etats_civils.keys())