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
8 changed files with 255 additions and 211 deletions
Showing only changes of commit f4c1d00046 - Show all commits

View File

@ -1,3 +1,11 @@
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
"""Affichages, debug
"""
from app import log
PE_DEBUG = 0
@ -15,5 +23,3 @@ else:
SANS_NOTE = "-"
NOM_STAT_GROUPE = "statistiques du groupe"
NOM_STAT_PROMO = "statistiques de la promo"

View File

@ -52,7 +52,6 @@ from app.scodoc import sco_formsemestre
from app.scodoc.sco_logos import find_logo
# Generated LaTeX files are encoded as:
PE_LATEX_ENCODING = "utf-8"
@ -98,7 +97,8 @@ def calcul_age(born: datetime.date) -> int:
return today.year - born.year - ((today.month, today.day) < (born.month, born.day))
def remove_accents(input_unicode_str):
# Nota: scu.suppress_accents fait la même chose mais renvoie un str et non un bytes
def remove_accents(input_unicode_str: str) -> bytes:
"""Supprime les accents d'une chaine unicode"""
nfkd_form = unicodedata.normalize("NFKD", input_unicode_str)
only_ascii = nfkd_form.encode("ASCII", "ignore")
@ -133,15 +133,15 @@ def escape_for_latex(s):
# ----------------------------------------------------------------------------------------
def list_directory_filenames(path):
"""List of regular filenames in a directory (recursive)
def list_directory_filenames(path: str) -> list[str]:
"""List of regular filenames (paths) in a directory (recursive)
Excludes files and directories begining with .
"""
R = []
paths = []
for root, dirs, files in os.walk(path, topdown=True):
dirs[:] = [d for d in dirs if d[0] != "."]
R += [os.path.join(root, fn) for fn in files if fn[0] != "."]
return R
paths += [os.path.join(root, fn) for fn in files if fn[0] != "."]
return paths
def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip):
@ -195,13 +195,15 @@ def add_pe_stuff_to_zip(zipfile, ziproot):
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
(en supposant qu'il n'y ait pas de redoublement à venir).
"""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 (en supposant qu'il n'y ait pas de redoublement à venir).
**Remarque sur le calcul** : Les semestres de 1ère partie d'année (S1, S3, S5 ou S4, S6 pour des semestres décalés)
s'étalent sur deux années civiles ; contrairement au semestre de seconde partie d'année universitaire.
**Remarque sur le calcul** : Les semestres de 1ère partie d'année (S1, S3, S5 ou S4,
S6 pour des semestres décalés)
s'étalent sur deux années civiles ; contrairement au semestre de seconde partie
d'année universitaire.
Par exemple :
@ -235,21 +237,22 @@ def get_annee_diplome_semestre(
if (
1 <= sem_id <= nbre_sem_formation
): # Si le semestre est un semestre BUT => problème si formation BUT en 1 an ??
nbreSemRestant = (
nb_sem_restants = (
nbre_sem_formation - sem_id
) # nombre de semestres restant avant diplome
nbreAnRestant = nbreSemRestant // 2 # nombre d'annees restant avant diplome
# Flag permettant d'activer ou désactiver un increment à prendre en compte en cas de semestre décalé
nb_annees_restantes = (
nb_sem_restants // 2
) # nombre d'annees restant avant diplome
# Flag permettant d'activer ou désactiver un increment
# à prendre en compte en cas de semestre décalé
# avec 1 - delta = 0 si semestre de 1ere partie d'année / 1 sinon
delta = annee_fin - annee_debut
decalage = nbreSemRestant % 2 # 0 si S4, 1 si S3, 0 si S2, 1 si S1
decalage = nb_sem_restants % 2 # 0 si S4, 1 si S3, 0 si S2, 1 si S1
increment = decalage * (1 - delta)
return annee_fin + nbreAnRestant + increment
return annee_fin + nb_annees_restantes + increment
def get_cosemestres_diplomants(
annee_diplome: int
) -> dict[int, FormSemestre]:
def get_cosemestres_diplomants(annee_diplome: int) -> dict[int, FormSemestre]:
"""Ensemble des cosemestres donnant lieu à diplomation à l'``annee_diplome``.
**Définition** : Un co-semestre est un semestre :
@ -264,13 +267,13 @@ def get_cosemestres_diplomants(
Returns:
Un dictionnaire {fid: FormSemestre(fid)} contenant les cosemestres
"""
tousLesSems = (
tous_les_sems = (
sco_formsemestre.do_formsemestre_list()
) # tous les semestres memorisés dans scodoc
cosemestres_fids = {
sem["id"]
for sem in tousLesSems
for sem in tous_les_sems
if get_annee_diplome_semestre(sem) == annee_diplome
}
@ -281,5 +284,3 @@ def get_cosemestres_diplomants(
cosemestres[fid] = cosem
return cosemestres

View File

@ -37,13 +37,13 @@ Created on 17/01/2024
"""
import pandas as pd
import app.pe.pe_rcs
from app.models import FormSemestre, Identite, Formation
from app.pe import pe_comp, pe_affichage
from app.scodoc import codes_cursus
from app.scodoc import sco_utils as scu
from app.comp.res_sem import load_formsemestre_results
class EtudiantsJuryPE:
"""Classe centralisant la gestion des étudiants à prendre en compte dans un jury de PE"""
@ -123,20 +123,15 @@ class EtudiantsJuryPE:
# Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris
self.etudiants_diplomes = self.get_etudiants_diplomes()
"""Les identités des étudiants diplômés"""
self.diplomes_ids = set(self.etudiants_diplomes.keys())
"""Les identifiants des étudiants diplômés"""
self.etudiants_ids = set(self.identites.keys())
"""Les identifiants des étudiants (diplômés, redoublants ou ayant abandonnés) à traiter"""
# Les abandons (pour debug)
self.abandons = self.get_etudiants_redoublants_ou_reorientes()
"""Les identités des étudiants ayant redoublés ou ayant abandonnés"""
# Les identités des étudiants ayant redoublés ou ayant abandonnés
self.abandons_ids = set(self.abandons)
"""Les identifiants des étudiants ayant redoublés ou ayant abandonnés"""
# Les identifiants des étudiants ayant redoublés ou ayant abandonnés
# Synthèse
pe_affichage.pe_print(
@ -288,18 +283,20 @@ class EtudiantsJuryPE:
}
self.cursus[etudid][f"S{i}"] = semestres_i
def get_formsemestres_terminaux_aggregat(self, aggregat: str):
def get_formsemestres_terminaux_aggregat(
self, aggregat: 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 :
* 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 formsemestre_id du S3 FI et du S3 UFA.
* les éventuelles situations de redoublement (par ex pour 1 étudiant ayant redoublé sa 2ème année :
S1 + S2 + S3 (1ère session) et S1 + S2 + S3 + S4 + S3 (2ème session), en renvoyant les formsemestre_id du
S3 (1ère session) et du S3 (2ème session)
* 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
formsemestre_id du S3 FI et du S3 UFA.
* les éventuelles situations de redoublement (par ex pour 1 étudiant ayant
redoublé sa 2ème année :
S1 + S2 + S3 (1ère session) et S1 + S2 + S3 + S4 + S3 (2ème session), en
renvoyant les formsemestre_id du S3 (1ère session) et du S3 (2ème session)
Args:
aggregat: L'aggrégat
@ -316,7 +313,6 @@ class EtudiantsJuryPE:
formsemestres_terminaux[fid] = trajectoire.formsemestre_final
return formsemestres_terminaux
def nbre_etapes_max_diplomes(self, etudids: list[int]) -> int:
"""Partant d'un ensemble d'étudiants,
nombre de semestres (étapes) maximum suivis par les étudiants du jury.
@ -407,7 +403,7 @@ def get_etudiants_dans_semestres(semestres: dict[int, FormSemestre]) -> set:
return etudiants_ids
def get_annee_diplome(etud: Identite) -> int:
def get_annee_diplome(etud: Identite) -> int | None:
"""L'année de diplôme prévue d'un étudiant en fonction de ses semestres
d'inscription (pour un BUT).
@ -415,13 +411,14 @@ def get_annee_diplome(etud: Identite) -> int:
identite: L'identité d'un étudiant
Returns:
L'année prévue de sa diplômation
L'année prévue de sa diplômation, ou None si aucun semestre
"""
formsemestres_apc = get_semestres_apc(etud)
if formsemestres_apc:
dates_possibles_diplome = []
"""Années de diplômation prédites en fonction des semestres (d'une formation APC) d'un étudiant"""
# Années de diplômation prédites en fonction des semestres
# (d'une formation APC) d'un étudiant
for sem_base in formsemestres_apc:
annee = pe_comp.get_annee_diplome_semestre(sem_base)
if annee:
@ -452,23 +449,26 @@ def get_semestres_apc(identite: Identite) -> list:
def arret_de_formation(etud: Identite, cosemestres: list[FormSemestre]) -> bool:
"""Détermine si un étudiant a arrêté sa formation. Il peut s'agir :
* d'une réorientation à l'initiative du jury de semestre ou d'une démission (on pourrait
utiliser les code NAR pour réorienté & DEM pour démissionnaire des résultats du jury renseigné dans la BDD,
mais pas nécessaire ici)
* d'une réorientation à l'initiative du jury de semestre ou d'une démission
(on pourrait utiliser les code NAR pour réorienté & DEM pour démissionnaire
des résultats du jury renseigné dans la BDD, mais pas nécessaire ici)
* d'un arrêt volontaire : l'étudiant disparait des listes d'inscrits (sans pour autant avoir été indiqué NAR ou DEM).
* d'un arrêt volontaire : l'étudiant disparait des listes d'inscrits (sans pour
autant avoir été indiqué NAR ou DEM).
Dans les cas, on considérera que l'étudiant a arrêté sa formation s'il n'est pas dans l'un des "derniers" cosemestres
(semestres conduisant à la même année de diplômation) connu dans Scodoc.
Dans les cas, on considérera que l'étudiant a arrêté sa formation s'il n'est pas
dans l'un des "derniers" cosemestres (semestres conduisant à la même année de diplômation)
connu dans Scodoc.
Par ex: au moment du jury PE en fin de S5 (pas de S6 renseigné dans Scodoc), l'étudiant doit appartenir à une
instance des S5 qui conduisent à la diplomation dans l'année visée. S'il n'est que dans un S4, il a sans doute
arrêté. A moins qu'il ne soit parti à l'étranger et , pas de notes.
Par ex: au moment du jury PE en fin de S5 (pas de S6 renseigné dans Scodoc),
l'étudiant doit appartenir à une instance des S5 qui conduisent à la diplomation dans
l'année visée. S'il n'est que dans un S4, il a sans doute arrêté. A moins qu'il ne soit
parti à l'étranger et là, pas de notes.
TODO:: Cas de l'étranger, à coder/tester
**Attention** : Cela suppose que toutes les instances d'un semestre donné (par ex: toutes les instances de S6
accueillant un étudiant soient créées ; sinon les étudiants non inscrits dans un S6 seront considérés comme
ayant abandonnés)
**Attention** : Cela suppose que toutes les instances d'un semestre donné
(par ex: toutes les instances de S6 accueillant un étudiant soient créées ; sinon les
étudiants non inscrits dans un S6 seront considérés comme ayant abandonnés)
TODO:: Peut-être à mettre en regard avec les propositions d'inscriptions d'étudiants dans un nouveau semestre
Pour chaque étudiant, recherche son dernier semestre en date (validé ou non) et

View File

@ -1,16 +1,59 @@
from app.comp import moy_sem
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
"""
Created on Thu Sep 8 09:36:33 2016
@author: barasc
"""
import pandas as pd
import numpy as np
from app.pe.pe_tabletags import TableTag, MoyenneTag
from app.pe.pe_etudiant import EtudiantsJuryPE
from app.pe.pe_rcs import RCS, RCSsJuryPE
from app.pe.pe_rcstag import RCSTag
import pandas as pd
import numpy as np
class RCSInterclasseTag(TableTag):
# -------------------------------------------------------------------------------------------------------------------
"""
Interclasse l'ensemble des étudiants diplômés à une année
donnée (celle du jury), pour un RCS donné (par ex: 'S2', '3S')
en reportant :
* les moyennes obtenues sur la trajectoire qu'il ont suivi pour atteindre
le numéro de semestre de fin de l'aggrégat (indépendamment de son
formsemestre)
* calculant le classement sur les étudiants diplômes
"""
def __init__(
self,
nom_rcs: str,
@ -18,15 +61,6 @@ class RCSInterclasseTag(TableTag):
rcss_jury_pe: RCSsJuryPE,
rcss_tags: dict[tuple, RCSTag],
):
"""
Interclasse l'ensemble des étudiants diplômés à une année
donnée (celle du jury), pour un RCS donné (par ex: 'S2', '3S')
en reportant :
* les moyennes obtenues sur la trajectoire qu'il ont suivi pour atteindre le numéro de semestre de fin de l'aggrégat (indépendamment de son
formsemestres)
* calculant le classement sur les étudiants diplômes
"""
TableTag.__init__(self)
self.nom_rcs = nom_rcs
@ -43,7 +77,8 @@ class RCSInterclasseTag(TableTag):
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.rcss: dict[int, RCS] = {}
"""Ensemble des trajectoires associées à l'aggrégat"""
for trajectoire_id in rcss_jury_pe.rcss:
@ -54,9 +89,7 @@ class RCSInterclasseTag(TableTag):
self.trajectoires_taggues: dict[int, RCS] = {}
"""Ensemble des trajectoires tagguées associées à l'aggrégat"""
for trajectoire_id in self.rcss:
self.trajectoires_taggues[trajectoire_id] = rcss_tags[
trajectoire_id
]
self.trajectoires_taggues[trajectoire_id] = rcss_tags[trajectoire_id]
# Les trajectoires suivies par les étudiants du jury, en ne gardant que
# celles associées aux diplomés

View File

@ -47,22 +47,18 @@ import os
from zipfile import ZipFile
import numpy as np
import pandas as pd
from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE
from app.pe.pe_etudiant import *
from app.pe.pe_rcs import *
import app.pe.pe_comp as pe_comp
import app.pe.pe_affichage as pe_affichage
from app.pe.pe_etudiant import * # TODO A éviter -> pe_etudiant.
from app.pe.pe_rcs import * # TODO A éviter
from app.pe.pe_rcstag import RCSTag
from app.pe.pe_semtag import SemestreTag
from app.pe.pe_interclasstag import RCSInterclasseTag
from app.pe.pe_rcstag import RCSTag
import app.pe.pe_affichage as pe_affichage
import pandas as pd
class JuryPE(object):
def __init__(self, diplome):
"""
Classe mémorisant toutes les informations nécessaires pour établir un jury de PE, sur la base
d'une année de diplôme. De ce semestre est déduit :
@ -72,6 +68,8 @@ class JuryPE(object):
Args:
diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX)
"""
def __init__(self, diplome):
self.diplome = diplome
"L'année du diplome"
@ -161,9 +159,7 @@ class JuryPE(object):
self.rcss.cree_rcss(self.etudiants)
# 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
)
@ -381,14 +377,13 @@ class JuryPE(object):
champ = (descr, "", "note")
notes_traj = moy_traj.get_notes()
donnees.loc[etudids_communs, champ] = notes_traj.loc[
etudids_communs]
etudids_communs
]
# Les rangs
champ = (descr, NOM_STAT_GROUPE, "class.")
rangs = moy_traj.get_rangs_inscrits()
donnees.loc[etudids_communs, champ] = rangs.loc[
etudids_communs
]
donnees.loc[etudids_communs, champ] = rangs.loc[etudids_communs]
# Les mins
champ = (descr, NOM_STAT_GROUPE, "min")
@ -487,9 +482,7 @@ class JuryPE(object):
# La trajectoire de l'étudiant sur l'aggrégat
trajectoire = self.rcss.suivi[etudid][aggregat]
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:
# L'interclassement
interclass = self.interclassements_taggues[aggregat]
@ -512,6 +505,7 @@ class JuryPE(object):
df.sort_values(by=[("", "", "tag")], inplace=True)
return df
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
@ -534,6 +528,7 @@ def get_formsemestres_etudiants(etudiants: EtudiantsJuryPE) -> dict:
semestres = semestres | etudiants.cursus[etudid][cle]
return semestres
def compute_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
@ -548,7 +543,7 @@ def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
Un dictionnaire {fid: SemestreTag(fid)}
"""
"""Création des semestres taggués, de type 'S1', 'S2', ..."""
# 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)

View File

@ -1,5 +1,15 @@
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
"""
Created on 01-2024
@author: barasc
"""
import app.pe.pe_comp as pe_comp
import app.pe.pe_affichage as pe_affichage
from app.models import FormSemestre
from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
@ -61,16 +71,16 @@ TYPES_RCS = {
}
"""Dictionnaire détaillant les différents regroupements cohérents
de semestres (RCS), en leur attribuant un nom et en détaillant
le nom des semestres qu'ils regroupement et l'affichage qui en sera fait
dans les tableurs de synthèse"""
le nom des semestres qu'ils regroupent et l'affichage qui en sera fait
dans les tableurs de synthèse.
"""
TOUS_LES_RCS_AVEC_PLUSIEURS_SEM = [cle for cle in TYPES_RCS.keys() if not cle.startswith("S")]
TOUS_LES_RCS_AVEC_PLUSIEURS_SEM = [cle for cle in TYPES_RCS if not cle.startswith("S")]
TOUS_LES_RCS = list(TYPES_RCS.keys())
TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS.keys() if cle.startswith("S")]
TOUS_LES_SEMESTRES = [cle for cle in TYPES_RCS if cle.startswith("S")]
class RCS:
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
"""Modélise un ensemble de semestres d'étudiants
associé à un type de regroupement cohérent de semestres
donné (par ex: 'S2', '3S', '2A').
@ -92,6 +102,8 @@ class RCS:
nom_rcs: Un nom du RCS (par ex: '5S')
semestre_final: Le semestre final du RCS
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
self.nom = nom_rcs
"""Nom du RCS"""
@ -121,14 +133,14 @@ class RCS:
semestre = self.semestres_aggreges[fid]
noms.append(f"S{semestre.semestre_id}({fid})")
noms = sorted(noms)
repr = f"{self.nom} ({self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"
title = f"""{self.nom} ({
self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"""
if verbose and noms:
repr += " - " + "+".join(noms)
return repr
title += " - " + "+".join(noms)
return title
class RCSsJuryPE:
def __init__(self, annee_diplome: int):
"""Classe centralisant toutes les regroupements cohérents de
semestres (RCS) des étudiants à prendre en compte dans un jury PE
@ -136,6 +148,7 @@ class RCSsJuryPE:
annee_diplome: L'année de diplomation
"""
def __init__(self, annee_diplome: int):
self.annee_diplome = annee_diplome
"""Année de diplômation"""
@ -155,7 +168,8 @@ class RCSsJuryPE:
"""
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)"""
# 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]
@ -167,14 +181,14 @@ class RCSsJuryPE:
+ TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
}
"""Le formsemestre terminal (dernier en date) associé au
semestre marquant la fin de l'aggrégat
(par ex: son dernier S3 en date)"""
# 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]
if semestres:
formsemestre_final = get_dernier_semestre_en_date(semestres)
"""Ajout ou récupération de la trajectoire"""
# 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)
@ -182,21 +196,22 @@ class RCSsJuryPE:
else:
trajectoire = self.rcss[trajectoire_id]
"""La liste des semestres de l'étudiant à prendre en compte
pour cette trajectoire"""
# 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
)
"""Ajout des semestres à la trajectoire"""
# Ajout des semestres à la trajectoire
trajectoire.add_semestres_a_aggreger(semestres_a_aggreger)
"""Mémoire la trajectoire suivie par l'étudiant"""
# Mémoire la trajectoire suivie par l'étudiant
self.suivi[etudid][nom_rcs] = trajectoire
def get_rcs_etudiant(semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
):
def get_rcs_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`.
@ -247,6 +262,7 @@ def get_rcs_etudiant(semestres: dict[int:FormSemestre], formsemestre_final: Form
semestres_aggreges[fid] = semestre
return semestres_aggreges
def get_descr_rcs(nom_rcs: str) -> str:
"""Renvoie la description pour les tableurs de synthèse
Excel d'un nom de RCS"""

View File

@ -35,33 +35,30 @@ Created on Fri Sep 9 09:15:05 2016
@author: barasc
"""
import numpy as np
import pandas as pd
import app.pe.pe_etudiant
from app import db, log, ScoValueError
from app.comp import res_sem, moy_ue, moy_sem
from app.comp.moy_sem import comp_ranks_series
from app.comp.res_compat import NotesTableCompat
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
from app.pe.pe_tabletags import TableTag, MoyenneTag
from app.scodoc import sco_tag_module
from app.scodoc.codes_cursus import UE_SPORT
import app.pe.pe_affichage as pe_affichage
from app.pe.pe_tabletags import TableTag, TAGS_RESERVES, MoyenneTag
import pandas as pd
class SemestreTag(TableTag):
def __init__(self, formsemestre_id: int):
"""
Un SemestreTag 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:
nom: Nom à donner au SemestreTag
formsemestre_id: Identifiant du ``FormSemestre`` sur lequel il se base
"""
TableTag.__init__(self)
@ -103,27 +100,27 @@ class SemestreTag(TableTag):
dict_ues_competences = get_noms_competences_from_ues(self.nt.formsemestre)
noms_tags_comp = list(set(dict_ues_competences.values()))
noms_tags_auto = ["but"] + noms_tags_comp
self.tags = (
noms_tags_perso + noms_tags_auto
)
self.tags = noms_tags_perso + noms_tags_auto
"""Tags du semestre taggué"""
## Vérifie l'unicité des tags
if len(set(self.tags)) != len(self.tags):
intersection = list(set(noms_tags_perso) & set(noms_tags_auto))
liste_intersection = "\n".join([f"<li><code>{tag}</code></li>" for tag in intersection])
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,
liste_intersection = "\n".join(
[f"<li><code>{tag}</code></li>" for tag in intersection]
)
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 :
contient le{s} tag{s} réservé{s} suivant :
<ul>
{liste_intersection}
</ul>
Modifiez votre programme de formation pour le(s) supprimer. Il(s) sera(ont) automatiquement à vos documents de poursuites d'études.
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
)
raise ScoValueError(message)
# Calcul des moyennes & les classements de chaque étudiant à chaque tag
self.moyennes_tags = {}
@ -174,25 +171,25 @@ class SemestreTag(TableTag):
La série des moyennes
"""
"""Adaptation du mask de calcul des moyennes au tag visé"""
# 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"""
# 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 tags_infos[tag]:
modimpls_mask[i] = False
"""Applique la pondération des coefficients"""
# 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 = moy_ue.compute_ue_moys_apc(
# 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,
@ -203,13 +200,13 @@ class SemestreTag(TableTag):
block=self.formsemestre.block_moyennes,
)
"""Les 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 = moy_sem.compute_sem_moys_apc_using_ects(
# 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(
moyennes_ues_tag,
ects,
formation_id=self.formsemestre.formation_id,
@ -224,11 +221,6 @@ def get_moduleimpl(modimpl_id) -> dict:
modimpl = db.session.get(ModuleImpl, modimpl_id)
if modimpl:
return modimpl
if SemestreTag.DEBUG:
log(
"SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas"
% (modimpl_id)
)
return None
@ -260,27 +252,26 @@ def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
"""
synthese_tags = {}
"""Instance des modules du semestre"""
# Instance des modules du semestre
modimpls = formsemestre.modimpls_sorted
for modimpl in modimpls:
modimpl_id = modimpl.id
"""Liste des tags pour le module concerné"""
# 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"
"""
# 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"""
# 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"""
# Ajout d'une clé pour le tag
if tagname not in synthese_tags:
synthese_tags[tagname] = {}
"""Ajout du module (modimpl) au tagname considéré"""
# 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
@ -298,6 +289,8 @@ 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
@ -310,8 +303,8 @@ def get_noms_competences_from_ues(formsemestre: FormSemestre) -> dict[int, str]:
noms_competences = {}
for ue in nt.ues:
if ue.type != UE_SPORT:
ordre = ue.niveau_competence.ordre
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

View File

@ -870,7 +870,7 @@ def stripquotes(s):
return s
def suppress_accents(s):
def suppress_accents(s: str) -> str:
"remove accents and suppress non ascii characters from string s"
if isinstance(s, str):
return (