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
5 changed files with 60 additions and 68 deletions
Showing only changes of commit d991eb007c - Show all commits

View File

@ -170,6 +170,7 @@ TOUS_LES_SEMESTRES = PARCOURS[AGGREGAT_DIPLOMANT]["aggregat"]
TOUS_LES_AGGREGATS = [cle for cle in PARCOURS.keys() if not cle.startswith("S")] TOUS_LES_AGGREGATS = [cle for cle in PARCOURS.keys() if not cle.startswith("S")]
TOUS_LES_PARCOURS = list(PARCOURS.keys()) TOUS_LES_PARCOURS = list(PARCOURS.keys())
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def calcul_age(born: datetime.date) -> int: def calcul_age(born: datetime.date) -> int:
"""Calcule l'age connaissant la date de naissance ``born``. (L'age est calculé """Calcule l'age connaissant la date de naissance ``born``. (L'age est calculé
@ -185,11 +186,7 @@ def calcul_age(born: datetime.date) -> int:
return None return None
today = datetime.date.today() today = datetime.date.today()
return ( return today.year - born.year - ((today.month, today.day) < (born.month, born.day))
today.year
- born.year
- ((today.month, today.day) < (born.month, born.day))
)
def remove_accents(input_unicode_str): def remove_accents(input_unicode_str):
@ -286,7 +283,9 @@ def add_pe_stuff_to_zip(zipfile, ziproot):
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def get_annee_diplome_semestre(sem_base, nbre_sem_formation=6) -> int: def get_annee_diplome_semestre(
sem_base: FormSemestre | dict, nbre_sem_formation: int = 6
) -> int:
"""Pour un semestre ``sem_base`` donné (supposé être un semestre d'une formation BUT à 6 semestres) """Pour un semestre ``sem_base`` donné (supposé être un semestre d'une formation BUT à 6 semestres)
et connaissant le numéro du semestre, ses dates de début et de fin du semestre, prédit l'année à laquelle et connaissant le numéro du semestre, ses dates de début et de fin du semestre, prédit l'année à laquelle
sera remis le diplôme BUT des étudiants qui y sont scolarisés sera remis le diplôme BUT des étudiants qui y sont scolarisés
@ -339,7 +338,9 @@ def get_annee_diplome_semestre(sem_base, nbre_sem_formation=6) -> int:
return annee_fin + nbreAnRestant + increment return annee_fin + nbreAnRestant + increment
def get_cosemestres_diplomants(annee_diplome: int, formation_id: int) -> list: def get_cosemestres_diplomants(
annee_diplome: int, formation_id: int
) -> dict[int, FormSemestre]:
"""Ensemble des cosemestres donnant lieu à diplomation à l'``annee_diplome`` """Ensemble des cosemestres donnant lieu à diplomation à l'``annee_diplome``
et s'intégrant à la formation donnée par son ``formation_id``. et s'intégrant à la formation donnée par son ``formation_id``.
@ -381,4 +382,3 @@ def get_cosemestres_diplomants(annee_diplome: int, formation_id: int) -> list:
cosemestres[fid] = cosem cosemestres[fid] = cosem
return cosemestres return cosemestres

View File

@ -66,11 +66,15 @@ class EtudiantsJuryPE:
"Les etudids des étudiants dont il faut calculer les moyennes/classements (même si d'éventuels abandons)" "Les etudids des étudiants dont il faut calculer les moyennes/classements (même si d'éventuels abandons)"
self.etudiants_ids = {} self.etudiants_ids = {}
"""Les étudiants inscrits dans les co-semestres (ceux du jury mais aussi d'autres ayant été réorientés ou ayant abandonnés)"""
self.cosemestres: dict[int, FormSemestre] = None
"Les cosemestres donnant lieu à même année de diplome"
def find_etudiants(self, formation_id: int): def find_etudiants(self, formation_id: int):
"""Liste des étudiants à prendre en compte dans le jury PE, en les recherchant """Liste des étudiants à prendre en compte dans le jury PE, en les recherchant
de manière automatique par rapport à leur année de diplomation ``annee_diplome`` de manière automatique par rapport à leur année de diplomation ``annee_diplome``
dans la formation ``formation_id``. dans la formation ``formation_id``. XXX TODO voir si on garde formation_id qui n'est pas utilisé ici
Les données obtenues sont stockées dans les attributs de EtudiantsJuryPE. Les données obtenues sont stockées dans les attributs de EtudiantsJuryPE.
@ -79,21 +83,18 @@ class EtudiantsJuryPE:
*Remarque* : ex: JuryPE.get_etudiants_in_jury() *Remarque* : ex: JuryPE.get_etudiants_in_jury()
""" """
"Les cosemestres donnant lieu à même année de diplome"
cosemestres = pe_comp.get_cosemestres_diplomants(self.annee_diplome, None) cosemestres = pe_comp.get_cosemestres_diplomants(self.annee_diplome, None)
self.cosemestres = cosemestres self.cosemestres = cosemestres
pe_comp.pe_print(
"1) Recherche des coSemestres -> %d trouvés" % len(cosemestres)
)
"""Les étudiants inscrits dans les co-semestres (ceux du jury mais aussi d'autres ayant été réorientés ou ayant abandonnés)""" pe_comp.pe_print(f"1) Recherche des coSemestres -> {len(cosemestres)} trouvés")
pe_comp.pe_print("2) Liste des étudiants dans les différents co-semestres") pe_comp.pe_print("2) Liste des étudiants dans les différents co-semestres")
self.etudiants_ids = get_etudiants_dans_semestres(cosemestres) self.etudiants_ids = get_etudiants_dans_semestres(cosemestres)
pe_comp.pe_print( pe_comp.pe_print(
" => %d étudiants trouvés dans les cosemestres" % len(self.etudiants_ids) " => %d étudiants trouvés dans les cosemestres" % len(self.etudiants_ids)
) )
"""Analyse des parcours étudiants pour déterminer leur année effective de diplome """Analyse des parcours étudiants pour déterminer leur année effective de diplome
avec prise en compte des redoublements, des abandons, ....""" avec prise en compte des redoublements, des abandons, ...."""
pe_comp.pe_print("3) Analyse des parcours individuels des étudiants") pe_comp.pe_print("3) Analyse des parcours individuels des étudiants")
@ -142,9 +143,13 @@ class EtudiantsJuryPE:
+ ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)]) + ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)])
) )
# Les abandons : # Les abandons :
self.abandons = sorted([self.cursus[etudid]['nom'] self.abandons = sorted(
for etudid in self.cursus if etudid not in self.diplomes_ids]) [
self.cursus[etudid]["nom"]
for etudid in self.cursus
if etudid not in self.diplomes_ids
]
)
def get_etudiants_diplomes(self) -> dict[int, Identite]: def get_etudiants_diplomes(self) -> dict[int, Identite]:
"""Identités des étudiants (sous forme d'un dictionnaire `{etudid: Identite(etudid)}` """Identités des étudiants (sous forme d'un dictionnaire `{etudid: Identite(etudid)}`
@ -198,10 +203,12 @@ class EtudiantsJuryPE:
"etudid": etudid, # les infos sur l'étudiant "etudid": etudid, # les infos sur l'étudiant
"etat_civil": identite.etat_civil, # Ajout à la table jury "etat_civil": identite.etat_civil, # Ajout à la table jury
"nom": identite.nom, "nom": identite.nom,
"entree": formsemestres[-1].date_debut.year, # La date d'entrée à l'IUT "entree": formsemestres[-1].date_debut.year, # La date d'entrée à l'IUT
"diplome": annee_diplome(identite), # Le date prévisionnelle de son diplôme "diplome": annee_diplome(identite), # Le date prévisionnelle de son diplôme
"formsemestres": semestres_etudiant, # les semestres de l'étudiant "formsemestres": semestres_etudiant, # les semestres de l'étudiant
"nb_semestres": len(semestres_etudiant), # le nombre de semestres de l'étudiant "nb_semestres": len(
semestres_etudiant
), # le nombre de semestres de l'étudiant
"abandon": False, # va être traité en dessous "abandon": False, # va être traité en dessous
} }
@ -250,7 +257,6 @@ class EtudiantsJuryPE:
} # les semestres de n°i de l'étudiant } # les semestres de n°i de l'étudiant
self.cursus[etudid][nom_sem] = semestres_i self.cursus[etudid][nom_sem] = semestres_i
def get_trajectoire(self, etudid: int, formsemestre_final: FormSemestre): def get_trajectoire(self, etudid: int, formsemestre_final: FormSemestre):
"""Ensemble des semestres parcourus par """Ensemble des semestres parcourus par
un étudiant pour l'amener à un semestre terminal. un étudiant pour l'amener à un semestre terminal.
@ -372,7 +378,7 @@ class EtudiantsJuryPE:
""" """
nbres_semestres = [] nbres_semestres = []
for etudid in self.diplomes_ids: for etudid in self.diplomes_ids:
nbres_semestres.append( self.cursus[etudid]["nb_semestres"] ) nbres_semestres.append(self.cursus[etudid]["nb_semestres"])
return max(nbres_semestres) return max(nbres_semestres)
@ -417,10 +423,7 @@ def annee_diplome(identite: Identite) -> int:
formsemestres = identite.get_formsemestres() formsemestres = identite.get_formsemestres()
if formsemestres: if formsemestres:
return max( return max(
[ [pe_comp.get_annee_diplome_semestre(sem_base) for sem_base in formsemestres]
pe_comp.get_annee_diplome_semestre(sem_base)
for sem_base in formsemestres
]
) )
else: else:
return None return None
@ -502,8 +505,6 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b
return False return False
def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSemestre: def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSemestre:
"""Renvoie le dernier semestre en **date de fin** d'un dictionnaire """Renvoie le dernier semestre en **date de fin** d'un dictionnaire
de semestres (potentiellement non trié) de la forme ``{fid: FormSemestre(fid)}``. de semestres (potentiellement non trié) de la forme ``{fid: FormSemestre(fid)}``.
@ -523,5 +524,3 @@ def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSeme
return dernier_semestre return dernier_semestre
else: else:
return None return None

View File

@ -1,4 +1,3 @@
from app.pe.pe_tabletags import TableTag from app.pe.pe_tabletags import TableTag
from app.pe.pe_etudiant import EtudiantsJuryPE from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE
@ -27,16 +26,14 @@ class AggregatInterclasseTag(TableTag):
trajectoires_jury_pe: TrajectoiresJuryPE, trajectoires_jury_pe: TrajectoiresJuryPE,
trajectoires_taggues: dict[tuple, TrajectoireTag], trajectoires_taggues: dict[tuple, TrajectoireTag],
): ):
"""""" # Table nommée au nom de l'aggrégat (par ex: 3S)
"""Table nommée au nom de l'aggrégat (par ex: 3S"""
TableTag.__init__(self, nom_aggregat) TableTag.__init__(self, nom_aggregat)
"""Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)""" """Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)""" # TODO
self.diplomes_ids = etudiants.etudiants_diplomes self.diplomes_ids = etudiants.etudiants_diplomes
self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids} self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids}
"""Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat # Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat
"""
self.trajectoires: dict[int, Trajectoire] = {} self.trajectoires: dict[int, Trajectoire] = {}
for trajectoire_id in trajectoires_jury_pe.trajectoires: for trajectoire_id in trajectoires_jury_pe.trajectoires:
trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id] trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id]
@ -49,8 +46,8 @@ class AggregatInterclasseTag(TableTag):
trajectoire_id trajectoire_id
] ]
"""Les trajectoires suivies par les étudiants du jury, en ne gardant que # Les trajectoires suivies par les étudiants du jury, en ne gardant que
celles associées aux diplomés""" # celles associées aux diplomés
self.suivi: dict[int, Trajectoire] = {} self.suivi: dict[int, Trajectoire] = {}
for etudid in self.diplomes_ids: for etudid in self.diplomes_ids:
self.suivi[etudid] = trajectoires_jury_pe.suivi[etudid][nom_aggregat] self.suivi[etudid] = trajectoires_jury_pe.suivi[etudid][nom_aggregat]
@ -58,10 +55,10 @@ class AggregatInterclasseTag(TableTag):
"""Les tags""" """Les tags"""
self.tags_sorted = self.do_taglist() self.tags_sorted = self.do_taglist()
"""Construit la matrice de notes""" # Construit la matrice de notes
self.notes = self.compute_notes_matrice() self.notes = self.compute_notes_matrice()
"""Synthétise les moyennes/classements par tag""" # Synthétise les moyennes/classements par tag
self.moyennes_tags = {} self.moyennes_tags = {}
for tag in self.tags_sorted: for tag in self.tags_sorted:
moy_gen_tag = self.notes[tag] moy_gen_tag = self.notes[tag]
@ -79,7 +76,6 @@ class AggregatInterclasseTag(TableTag):
"""Une représentation textuelle""" """Une représentation textuelle"""
return f"Aggrégat {self.nom}" return f"Aggrégat {self.nom}"
def do_taglist(self): def do_taglist(self):
"""Synthétise les tags à partir des trajectoires_tagguées """Synthétise les tags à partir des trajectoires_tagguées
@ -87,39 +83,35 @@ class AggregatInterclasseTag(TableTag):
Une liste de tags triés par ordre alphabétique Une liste de tags triés par ordre alphabétique
""" """
tags = [] tags = []
for trajectoire_id in self.trajectoires_taggues: for trajectoire in self.trajectoires_taggues.values():
trajectoire = self.trajectoires_taggues[trajectoire_id]
tags.extend(trajectoire.tags_sorted) tags.extend(trajectoire.tags_sorted)
return sorted(set(tags)) return sorted(set(tags))
def compute_notes_matrice(self): def compute_notes_matrice(self):
"""Construit la matrice de notes (etudid x tags) """Construit la matrice de notes (etudid x tags)
retraçant les moyennes obtenues par les étudiants dans les semestres associés à retraçant les moyennes obtenues par les étudiants dans les semestres associés à
l'aggrégat (une trajectoire ayant pour numéro de semestre final, celui de l'aggrégat). l'aggrégat (une trajectoire ayant pour numéro de semestre final, celui de l'aggrégat).
""" """
nb_tags = len(self.tags_sorted) # nb_tags = len(self.tags_sorted) unused ?
nb_etudiants = len(self.diplomes_ids) # nb_etudiants = len(self.diplomes_ids)
"""Index de la matrice (etudids -> dim 0, tags -> dim 1)""" # Index de la matrice (etudids -> dim 0, tags -> dim 1)
etudids = list(self.diplomes_ids) etudids = list(self.diplomes_ids)
tags = self.tags_sorted tags = self.tags_sorted
"""Partant d'un dataframe vierge""" # Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids, columns=tags) df = pd.DataFrame(np.nan, index=etudids, columns=tags)
for trajectoire_id in self.trajectoires_taggues: for notes in self.trajectoires_taggues.values():
"""Charge les moyennes par tag de la trajectoire tagguée""" # Charge les moyennes par tag de la trajectoire tagguée
notes = self.trajectoires_taggues[trajectoire_id].notes
"""Etudiants/Tags communs entre la trajectoire_tagguée et les données interclassées""" # Etudiants/Tags communs entre la trajectoire_tagguée et les données interclassées
etudids_communs = df.index.intersection(notes.index) etudids_communs = df.index.intersection(notes.index)
tags_communs = df.columns.intersection(notes.columns) tags_communs = df.columns.intersection(notes.columns)
"""Injecte les notes par tag""" # Injecte les notes par tag
df.loc[etudids_communs, tags_communs] = notes.loc[ df.loc[etudids_communs, tags_communs] = notes.loc[
etudids_communs, tags_communs etudids_communs, tags_communs
] ]
return df return df

View File

@ -107,13 +107,14 @@ class JuryPE(object):
self.formation_id = formation_id self.formation_id = formation_id
"Un zip où ranger les fichiers générés" "Un zip où ranger les fichiers générés"
self.nom_export_zip = "Jury_PE_%s" % self.diplome self.nom_export_zip = f"Jury_PE_{self.diplome}"
self.zipdata = io.BytesIO() self.zipdata = io.BytesIO()
self.zipfile = ZipFile(self.zipdata, "w") self.zipfile = ZipFile(self.zipdata, "w")
"""Chargement des étudiants à prendre en compte dans le jury""" """Chargement des étudiants à prendre en compte dans le jury"""
pe_comp.pe_print( pe_comp.pe_print(
f"*** Recherche et chargement des étudiants diplômés en {self.diplome} pour la formation {self.formation_id}" f"""*** Recherche et chargement des étudiants diplômés en {
self.diplome} pour la formation {self.formation_id}"""
) )
self.etudiants = EtudiantsJuryPE(self.diplome) # Les infos sur les étudiants self.etudiants = EtudiantsJuryPE(self.diplome) # Les infos sur les étudiants
self.etudiants.find_etudiants(self.formation_id) self.etudiants.find_etudiants(self.formation_id)
@ -133,7 +134,7 @@ class JuryPE(object):
filename, formsemestretag.str_tagtable(), path="details_semestres" filename, formsemestretag.str_tagtable(), path="details_semestres"
) )
"""Génère les trajectoires (combinaison de semestres suivis """Génère les trajectoires (combinaison de semestres suivis
par un étudiant pour atteindre le semestre final d'un aggrégat) par un étudiant pour atteindre le semestre final d'un aggrégat)
""" """
pe_comp.pe_print( pe_comp.pe_print(
@ -197,7 +198,7 @@ class JuryPE(object):
open(filename, "rb").read(), open(filename, "rb").read(),
) )
"""Fin !!!! Tada :)""" # Fin !!!! Tada :)
def add_file_to_zip(self, filename: str, data, path=""): def add_file_to_zip(self, filename: str, data, path=""):
"""Add a file to our zip """Add a file to our zip
@ -256,7 +257,7 @@ class JuryPE(object):
etudids = list(self.diplomes_ids) etudids = list(self.diplomes_ids)
"""Récupération des données des étudiants""" # Récupération des données des étudiants
administratif = {} administratif = {}
nbre_semestres_max = self.etudiants.nbre_etapes_max_diplomes() nbre_semestres_max = self.etudiants.nbre_etapes_max_diplomes()
@ -279,11 +280,11 @@ class JuryPE(object):
etapes = pe_affichage.etapes_du_cursus(formsemestres, nbre_semestres_max) etapes = pe_affichage.etapes_du_cursus(formsemestres, nbre_semestres_max)
administratif[etudid] |= etapes administratif[etudid] |= etapes
"""Construction du dataframe""" # Construction du dataframe
df = pd.DataFrame.from_dict(administratif, orient="index") df = pd.DataFrame.from_dict(administratif, orient="index")
"""Tri par nom/prénom""" # Tri par nom/prénom
df.sort_values(by=["Nom", "Prenom"], inplace = True) df.sort_values(by=["Nom", "Prenom"], inplace=True)
return df return df
def df_tag(self, tag): def df_tag(self, tag):
@ -348,11 +349,11 @@ class JuryPE(object):
} }
# Fin de l'aggrégat # Fin de l'aggrégat
"""Construction du dataFrame""" # Construction du dataFrame
df = pd.DataFrame.from_dict(donnees, orient="index") df = pd.DataFrame.from_dict(donnees, orient="index")
"""Tri par nom/prénom""" # Tri par nom/prénom
df.sort_values(by=["Nom", "Prenom"], inplace = True) df.sort_values(by=["Nom", "Prenom"], inplace=True)
return df return df
def table_syntheseJury(self, mode="singlesheet"): # was str_syntheseJury def table_syntheseJury(self, mode="singlesheet"): # was str_syntheseJury
@ -400,10 +401,10 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
pe_comp.pe_print(f" --> Semestre taggué {nom} sur la base de {formsemestre}") pe_comp.pe_print(f" --> Semestre taggué {nom} sur la base de {formsemestre}")
"""Créé le semestre_tag et exécute les calculs de moyennes""" # Crée le semestre_tag et exécute les calculs de moyennes
formsemestretag = SemestreTag(nom, frmsem_id) formsemestretag = SemestreTag(nom, frmsem_id)
"""Stocke le semestre taggué""" # Stocke le semestre taggué
semestres_tags[frmsem_id] = formsemestretag semestres_tags[frmsem_id] = formsemestretag
return semestres_tags return semestres_tags

View File

@ -63,7 +63,7 @@ def _pe_view_sem_recap_form(formsemestre_id):
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
voir la documentation voir la documentation
</a>. </a>.
Cette fonction (en Scodoc9) n'est prévue que pour le BUT. Cette fonction (en Scodoc9) n'est prévue que pour le BUT.
<br> <br>
Rendez-vous donc sur un semestre de BUT. Rendez-vous donc sur un semestre de BUT.
</p> </p>