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_PARCOURS = list(PARCOURS.keys())
# ----------------------------------------------------------------------------------------
def calcul_age(born: datetime.date) -> int:
"""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
today = datetime.date.today()
return (
today.year
- born.year
- ((today.month, today.day) < (born.month, born.day))
)
return today.year - born.year - ((today.month, today.day) < (born.month, born.day))
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)
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
@ -339,7 +338,9 @@ def get_annee_diplome_semestre(sem_base, nbre_sem_formation=6) -> int:
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``
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
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)"
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):
"""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``
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.
@ -79,21 +83,18 @@ class EtudiantsJuryPE:
*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)
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")
self.etudiants_ids = get_etudiants_dans_semestres(cosemestres)
pe_comp.pe_print(
" => %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, ...."""
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)])
)
# Les abandons :
self.abandons = sorted([self.cursus[etudid]['nom']
for etudid in self.cursus if etudid not in self.diplomes_ids])
self.abandons = sorted(
[
self.cursus[etudid]["nom"]
for etudid in self.cursus
if etudid not in self.diplomes_ids
]
)
def get_etudiants_diplomes(self) -> dict[int, Identite]:
"""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
"etat_civil": identite.etat_civil, # Ajout à la table jury
"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
"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
}
@ -250,7 +257,6 @@ class EtudiantsJuryPE:
} # les semestres de n°i de l'étudiant
self.cursus[etudid][nom_sem] = semestres_i
def get_trajectoire(self, etudid: int, formsemestre_final: FormSemestre):
"""Ensemble des semestres parcourus par
un étudiant pour l'amener à un semestre terminal.
@ -372,7 +378,7 @@ class EtudiantsJuryPE:
"""
nbres_semestres = []
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)
@ -417,10 +423,7 @@ def annee_diplome(identite: Identite) -> int:
formsemestres = identite.get_formsemestres()
if formsemestres:
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:
return None
@ -502,8 +505,6 @@ def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> b
return False
def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSemestre:
"""Renvoie le dernier semestre en **date de fin** d'un dictionnaire
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
else:
return None

View File

@ -1,4 +1,3 @@
from app.pe.pe_tabletags import TableTag
from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE
@ -27,16 +26,14 @@ class AggregatInterclasseTag(TableTag):
trajectoires_jury_pe: TrajectoiresJuryPE,
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)
"""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.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] = {}
for trajectoire_id in trajectoires_jury_pe.trajectoires:
trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id]
@ -49,8 +46,8 @@ class AggregatInterclasseTag(TableTag):
trajectoire_id
]
"""Les trajectoires suivies par les étudiants du jury, en ne gardant que
celles associées aux diplomés"""
# Les trajectoires suivies par les étudiants du jury, en ne gardant que
# celles associées aux diplomés
self.suivi: dict[int, Trajectoire] = {}
for etudid in self.diplomes_ids:
self.suivi[etudid] = trajectoires_jury_pe.suivi[etudid][nom_aggregat]
@ -58,10 +55,10 @@ class AggregatInterclasseTag(TableTag):
"""Les tags"""
self.tags_sorted = self.do_taglist()
"""Construit la matrice de notes"""
# Construit la matrice de notes
self.notes = self.compute_notes_matrice()
"""Synthétise les moyennes/classements par tag"""
# Synthétise les moyennes/classements par tag
self.moyennes_tags = {}
for tag in self.tags_sorted:
moy_gen_tag = self.notes[tag]
@ -79,7 +76,6 @@ class AggregatInterclasseTag(TableTag):
"""Une représentation textuelle"""
return f"Aggrégat {self.nom}"
def do_taglist(self):
"""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
"""
tags = []
for trajectoire_id in self.trajectoires_taggues:
trajectoire = self.trajectoires_taggues[trajectoire_id]
for trajectoire in self.trajectoires_taggues.values():
tags.extend(trajectoire.tags_sorted)
return sorted(set(tags))
def compute_notes_matrice(self):
"""Construit la matrice de notes (etudid x tags)
retraçant les moyennes obtenues par les étudiants dans les semestres associés à
l'aggrégat (une trajectoire ayant pour numéro de semestre final, celui de l'aggrégat).
"""
nb_tags = len(self.tags_sorted)
nb_etudiants = len(self.diplomes_ids)
# nb_tags = len(self.tags_sorted) unused ?
# 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)
tags = self.tags_sorted
"""Partant d'un dataframe vierge"""
# Partant d'un dataframe vierge
df = pd.DataFrame(np.nan, index=etudids, columns=tags)
for trajectoire_id in self.trajectoires_taggues:
"""Charge les moyennes par tag de la trajectoire tagguée"""
notes = self.trajectoires_taggues[trajectoire_id].notes
for notes in self.trajectoires_taggues.values():
# Charge les moyennes par tag de la trajectoire tagguée
"""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)
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[
etudids_communs, tags_communs
]
return df

View File

@ -107,13 +107,14 @@ class JuryPE(object):
self.formation_id = formation_id
"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.zipfile = ZipFile(self.zipdata, "w")
"""Chargement des étudiants à prendre en compte dans le jury"""
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.find_etudiants(self.formation_id)
@ -133,7 +134,7 @@ class JuryPE(object):
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)
"""
pe_comp.pe_print(
@ -197,7 +198,7 @@ class JuryPE(object):
open(filename, "rb").read(),
)
"""Fin !!!! Tada :)"""
# Fin !!!! Tada :)
def add_file_to_zip(self, filename: str, data, path=""):
"""Add a file to our zip
@ -256,7 +257,7 @@ class JuryPE(object):
etudids = list(self.diplomes_ids)
"""Récupération des données des étudiants"""
# Récupération des données des étudiants
administratif = {}
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)
administratif[etudid] |= etapes
"""Construction du dataframe"""
# Construction du dataframe
df = pd.DataFrame.from_dict(administratif, orient="index")
"""Tri par nom/prénom"""
df.sort_values(by=["Nom", "Prenom"], inplace = True)
# Tri par nom/prénom
df.sort_values(by=["Nom", "Prenom"], inplace=True)
return df
def df_tag(self, tag):
@ -348,11 +349,11 @@ class JuryPE(object):
}
# Fin de l'aggrégat
"""Construction du dataFrame"""
# Construction du dataFrame
df = pd.DataFrame.from_dict(donnees, orient="index")
"""Tri par nom/prénom"""
df.sort_values(by=["Nom", "Prenom"], inplace = True)
# Tri par nom/prénom
df.sort_values(by=["Nom", "Prenom"], inplace=True)
return df
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}")
"""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)
"""Stocke le semestre taggué"""
# Stocke le semestre taggué
semestres_tags[frmsem_id] = formsemestretag
return semestres_tags

View File

@ -63,7 +63,7 @@ def _pe_view_sem_recap_form(formsemestre_id):
target="_blank" rel="noopener noreferrer">
voir la documentation
</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>
Rendez-vous donc sur un semestre de BUT.
</p>