Compare commits
No commits in common. "99bb0f471b391ca0e3da80d1bcbbe300619603d5" and "7eb41fb2eb222532d40ce5bf36caf06450d60be9" have entirely different histories.
99bb0f471b
...
7eb41fb2eb
@ -393,7 +393,7 @@ class BulletinBUT:
|
||||
else:
|
||||
etud_ues_ids = res.etud_ues_ids(etud.id)
|
||||
|
||||
nbabsnj, nbabsjust, nbabs = formsemestre.get_abs_count(etud.id)
|
||||
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
|
||||
etud_groups = sco_groups.get_etud_formsemestre_groups(
|
||||
etud, formsemestre, only_to_show=True
|
||||
)
|
||||
@ -408,7 +408,7 @@ class BulletinBUT:
|
||||
}
|
||||
if self.prefs["bul_show_abs"]:
|
||||
semestre_infos["absences"] = {
|
||||
"injustifie": nbabsnj,
|
||||
"injustifie": nbabs - nbabsjust,
|
||||
"total": nbabs,
|
||||
"metrique": {
|
||||
"H.": "Heure(s)",
|
||||
@ -525,7 +525,7 @@ class BulletinBUT:
|
||||
d["demission"] = ""
|
||||
|
||||
# --- Absences
|
||||
_, d["nbabsjust"], d["nbabs"] = self.res.formsemestre.get_abs_count(etud.id)
|
||||
d["nbabs"], d["nbabsjust"] = self.res.formsemestre.get_abs_count(etud.id)
|
||||
|
||||
# --- Decision Jury
|
||||
infos, _ = sco_bulletins.etud_descr_situation_semestre(
|
||||
@ -540,9 +540,9 @@ class BulletinBUT:
|
||||
|
||||
d.update(infos)
|
||||
# --- Rangs
|
||||
d[
|
||||
"rang_nt"
|
||||
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||
d["rang_nt"] = (
|
||||
f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||
)
|
||||
d["rang_txt"] = "Rang " + d["rang_nt"]
|
||||
|
||||
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
|
||||
|
@ -241,7 +241,7 @@ def bulletin_but_xml_compat(
|
||||
|
||||
# --- Absences
|
||||
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
|
||||
_, nbabsjust, nbabs = formsemestre.get_abs_count(etud.id)
|
||||
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
|
||||
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
|
||||
|
||||
# -------- LA SUITE EST COPIEE SANS MODIF DE sco_bulletins_xml.py ---------
|
||||
|
@ -875,7 +875,7 @@ class FormSemestre(db.Model):
|
||||
|
||||
def get_abs_count(self, etudid):
|
||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||
tuple (nb abs non just, nb abs justifiées, nb abs total)
|
||||
tuple (nb abs, nb abs justifiées)
|
||||
Utilise un cache.
|
||||
"""
|
||||
from app.scodoc import sco_assiduites
|
||||
|
@ -119,7 +119,7 @@ class RCSemXTag(pe_tabletags.TableTag):
|
||||
)
|
||||
"""Compétences (triées par nom, extraites des SxTag aggrégés)"""
|
||||
aff = pe_affichage.repr_comp_et_ues(self.acronymes_ues_to_competences)
|
||||
pe_affichage.pe_print(f"--> Compétences : {aff}")
|
||||
pe_affichage.pe_print(f"--> Compétences : {', '.join(self.competences_sorted)}")
|
||||
|
||||
# Les tags
|
||||
self.tags_sorted = self._do_taglist()
|
||||
@ -134,24 +134,28 @@ class RCSemXTag(pe_tabletags.TableTag):
|
||||
for tag in self.tags_sorted:
|
||||
pe_affichage.pe_print(f"--> Moyennes du tag 👜{tag}")
|
||||
|
||||
# Cubes d'inscription (etudids_sorted x compétences_sorted x sxstags),
|
||||
# de notes et de coeffs pour la moyenne générale
|
||||
# en "aggrégant" les données des sxstags, compétence par compétence
|
||||
(
|
||||
inscr_df,
|
||||
inscr_cube,
|
||||
notes_df,
|
||||
notes_cube,
|
||||
coeffs_df,
|
||||
coeffs_cube,
|
||||
) = self.compute_cubes(tag)
|
||||
# Traitement des inscriptions aux semX(tags)
|
||||
# ******************************************
|
||||
# Cube d'inscription (etudids_sorted x compétences_sorted x sxstags)
|
||||
# indiquant quel sxtag est valide pour chaque étudiant
|
||||
inscr_df, inscr_cube = self.compute_inscriptions_comps_cube(tag)
|
||||
|
||||
# Calcule les moyennes, et synthétise les coeffs
|
||||
(
|
||||
moys_competences,
|
||||
matrice_coeffs_moy_gen,
|
||||
) = self.compute_notes_et_coeffs_competences(
|
||||
notes_cube, coeffs_cube, inscr_cube
|
||||
# Traitement des notes
|
||||
# ********************
|
||||
# Cube de notes (etudids_sorted x compétences_sorted x sxstags)
|
||||
notes_df, notes_cube = self.compute_notes_comps_cube(tag)
|
||||
# Calcule les moyennes sous forme d'un dataframe en les "aggrégant"
|
||||
# compétence par compétence
|
||||
moys_competences = self.compute_notes_competences(notes_cube, inscr_cube)
|
||||
|
||||
# Traitement des coeffs pour la moyenne générale
|
||||
# ***********************************************
|
||||
# Df des coeffs sur tous les SxTags aggrégés
|
||||
coeffs_df, coeffs_cube = self.compute_coeffs_comps_cube(tag)
|
||||
|
||||
# Synthèse des coefficients à prendre en compte pour la moyenne générale
|
||||
matrice_coeffs_moy_gen = self.compute_coeffs_competences(
|
||||
coeffs_cube, inscr_cube, notes_cube
|
||||
)
|
||||
|
||||
# Affichage des coeffs
|
||||
@ -182,17 +186,70 @@ class RCSemXTag(pe_tabletags.TableTag):
|
||||
else:
|
||||
return f"{self.__class__.__name__} {self.rcs_id}"
|
||||
|
||||
def compute_cubes(self, tag):
|
||||
"""Pour un tag donné, construit les cubes de :
|
||||
* d'inscriptions aux compétences (etudid x competences x SxTag)
|
||||
* de notes (etudid x competences x SxTag)
|
||||
* de coeffs (etudid x competences x SxTag)
|
||||
def compute_notes_comps_cube(self, tag):
|
||||
"""Pour un tag donné, construit le cube de notes (etudid x competences x SxTag)
|
||||
nécessaire au calcul des moyennes,
|
||||
en remplaçant les données d'UE (obtenus du SxTag) par les compétences
|
||||
|
||||
nécessaire au calcul des moyennes, en :
|
||||
Args:
|
||||
tag: Le tag visé
|
||||
"""
|
||||
# etudids_sorted: list[int],
|
||||
# competences_sorted: list[str],
|
||||
# sxstags: dict[(str, int) : pe_sxtag.SxTag],
|
||||
notes_dfs = {}
|
||||
|
||||
* transformant les données des UEs en données de compétences (changement de noms)
|
||||
* fusionnant les données d'un même semestre, lorsque plusieurs UEs traitent d'une même compétence (cas des RCSx = Sx)
|
||||
* aggrégeant les données de compétences sur plusieurs semestres (cas des RCSx = xA ou xS)
|
||||
for sxtag_id, sxtag in self.sxstags_aggreges.items():
|
||||
# Partant d'un dataframe vierge
|
||||
notes_df = pd.DataFrame(
|
||||
np.nan, index=self.etudids_sorted, columns=self.competences_sorted
|
||||
)
|
||||
# Charge les notes du semestre tag (copie car changement de nom de colonnes à venir)
|
||||
if tag in sxtag.moyennes_tags: # si le tag est présent dans le semestre
|
||||
moys_tag = sxtag.moyennes_tags[tag]
|
||||
|
||||
notes = moys_tag.matrice_notes_gen.copy() # dataframe etudids x ues
|
||||
|
||||
# Traduction des acronymes d'UE en compétences
|
||||
acronymes_ues_columns = notes.columns
|
||||
acronymes_to_comps = [
|
||||
self.acronymes_ues_to_competences[acro]
|
||||
for acro in acronymes_ues_columns
|
||||
]
|
||||
notes.columns = acronymes_to_comps
|
||||
|
||||
# Les étudiants et les compétences communes
|
||||
(
|
||||
etudids_communs,
|
||||
comp_communes,
|
||||
) = pe_comp.find_index_and_columns_communs(notes_df, notes)
|
||||
|
||||
# Recopie des notes et des coeffs
|
||||
notes_df.loc[etudids_communs, comp_communes] = notes.loc[
|
||||
etudids_communs, comp_communes
|
||||
]
|
||||
|
||||
# Supprime tout ce qui n'est pas numérique
|
||||
# for col in notes_df.columns:
|
||||
# notes_df[col] = pd.to_numeric(notes_df[col], errors="coerce")
|
||||
|
||||
# Stocke les dfs
|
||||
notes_dfs[sxtag_id] = notes_df
|
||||
|
||||
"""Réunit les notes sous forme d'un cube etudids x competences x semestres"""
|
||||
sxtag_x_etudids_x_comps = [
|
||||
notes_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
|
||||
]
|
||||
notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
|
||||
|
||||
return notes_dfs, notes_etudids_x_comps_x_sxtag
|
||||
|
||||
def compute_coeffs_comps_cube(self, tag):
|
||||
"""Pour un tag donné, construit
|
||||
le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions
|
||||
des étudiants aux UEs en fonction de leur parcours)
|
||||
qui s'applique aux différents SxTag
|
||||
en remplaçant les données d'UE (obtenus du SxTag) par les compétences
|
||||
|
||||
Args:
|
||||
tag: Le tag visé
|
||||
@ -201,74 +258,83 @@ class RCSemXTag(pe_tabletags.TableTag):
|
||||
# competences_sorted: list[str],
|
||||
# sxstags: dict[(str, int) : pe_sxtag.SxTag],
|
||||
|
||||
inscriptions_dfs = {}
|
||||
notes_dfs = {}
|
||||
coeffs_dfs = {}
|
||||
|
||||
for sxtag_id, sxtag in self.sxstags_aggreges.items():
|
||||
# Partant de dataframes vierges
|
||||
inscription_df = pd.DataFrame(
|
||||
np.nan, index=self.etudids_sorted, columns=self.competences_sorted
|
||||
)
|
||||
notes_df = pd.DataFrame(
|
||||
np.nan, index=self.etudids_sorted, columns=self.competences_sorted
|
||||
)
|
||||
# Partant d'un dataframe vierge
|
||||
coeffs_df = pd.DataFrame(
|
||||
np.nan, index=self.etudids_sorted, columns=self.competences_sorted
|
||||
)
|
||||
|
||||
# Charge les données du semestre tag (copie car changement de nom de colonnes à venir)
|
||||
if tag in sxtag.moyennes_tags: # si le tag est présent dans le semestre
|
||||
if tag in sxtag.moyennes_tags:
|
||||
moys_tag = sxtag.moyennes_tags[tag]
|
||||
|
||||
# Les inscr, les notes, les coeffs
|
||||
acro_ues_inscr_parcours = sxtag.acro_ues_inscr_parcours
|
||||
notes = moys_tag.matrice_notes_gen
|
||||
coeffs = moys_tag.matrice_coeffs_moy_gen # les coeffs
|
||||
# Charge les notes et les coeffs du semestre tag
|
||||
coeffs = moys_tag.matrice_coeffs_moy_gen.copy() # les coeffs
|
||||
|
||||
# Traduction des acronymes d'UE en compétences
|
||||
# comp_to_ues = pe_comp.asso_comp_to_accronymes(self.acronymes_ues_to_competences)
|
||||
acronymes_ues_columns = notes.columns
|
||||
for acronyme in acronymes_ues_columns:
|
||||
# La compétence visée
|
||||
competence = self.acronymes_ues_to_competences[acronyme] # La comp
|
||||
acronymes_ues_columns = coeffs.columns
|
||||
acronymes_to_comps = [
|
||||
self.acronymes_ues_to_competences[acro]
|
||||
for acro in acronymes_ues_columns
|
||||
]
|
||||
coeffs.columns = acronymes_to_comps
|
||||
|
||||
# Les étud inscrits à la comp reportés dans l'inscription au RCSemX
|
||||
comp_inscr = acro_ues_inscr_parcours[
|
||||
acro_ues_inscr_parcours.notnull()
|
||||
].index
|
||||
etudids_communs = list(
|
||||
inscription_df.index.intersection(comp_inscr)
|
||||
# Les étudiants et les compétences communes
|
||||
etudids_communs, comp_communes = pe_comp.find_index_and_columns_communs(
|
||||
coeffs_df, coeffs
|
||||
)
|
||||
inscription_df.loc[
|
||||
etudids_communs, competence
|
||||
] = acro_ues_inscr_parcours.loc[etudids_communs, acronyme]
|
||||
|
||||
# Les étud ayant une note à l'acronyme de la comp (donc à la comp)
|
||||
etuds_avec_notes = notes[notes[acronyme].notnull()].index
|
||||
etudids_communs = list(
|
||||
notes_df.index.intersection(etuds_avec_notes)
|
||||
)
|
||||
notes_df.loc[etudids_communs, competence] = notes.loc[
|
||||
etudids_communs, acronyme
|
||||
# Recopie des notes et des coeffs
|
||||
coeffs_df.loc[etudids_communs, comp_communes] = coeffs.loc[
|
||||
etudids_communs, comp_communes
|
||||
]
|
||||
|
||||
# Les coeffs
|
||||
etuds_avec_coeffs = coeffs[coeffs[acronyme].notnull()].index
|
||||
etudids_communs = list(
|
||||
coeffs_df.index.intersection(etuds_avec_coeffs)
|
||||
)
|
||||
coeffs_df.loc[etudids_communs, competence] = coeffs.loc[
|
||||
etudids_communs, acronyme
|
||||
# Stocke les dfs
|
||||
coeffs_dfs[sxtag_id] = coeffs_df
|
||||
|
||||
"""Réunit les coeffs sous forme d'un cube etudids x competences x semestres"""
|
||||
sxtag_x_etudids_x_comps = [
|
||||
coeffs_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
|
||||
]
|
||||
# Supprime tout ce qui n'est pas numérique
|
||||
# for col in notes_df.columns:
|
||||
# notes_df[col] = pd.to_numeric(notes_df[col], errors="coerce")
|
||||
coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
|
||||
|
||||
return coeffs_dfs, coeffs_etudids_x_comps_x_sxtag
|
||||
|
||||
def compute_inscriptions_comps_cube(
|
||||
self,
|
||||
tag,
|
||||
):
|
||||
"""Pour un tag donné, construit
|
||||
le cube etudid x competences x SxTag traduisant quels sxtags est à prendre
|
||||
en compte pour chaque étudiant.
|
||||
Contient des 0 et des 1 pour indiquer la prise en compte.
|
||||
|
||||
Args:
|
||||
tag: Le tag visé
|
||||
"""
|
||||
# etudids_sorted: list[int],
|
||||
# competences_sorted: list[str],
|
||||
# sxstags: dict[(str, int) : pe_sxtag.SxTag],
|
||||
# Initialisation
|
||||
inscriptions_dfs = {}
|
||||
|
||||
for sxtag_id, sxtag in self.sxstags_aggreges.items():
|
||||
# Partant d'un dataframe vierge
|
||||
inscription_df = pd.DataFrame(
|
||||
0, index=self.etudids_sorted, columns=self.competences_sorted
|
||||
)
|
||||
|
||||
# Les étudiants dont les résultats au sxtag ont été calculés
|
||||
etudids_sxtag = sxtag.etudids_sorted
|
||||
|
||||
# Les étudiants communs
|
||||
etudids_communs = sorted(set(self.etudids_sorted) & set(etudids_sxtag))
|
||||
|
||||
# Acte l'inscription
|
||||
inscription_df.loc[etudids_communs, :] = 1
|
||||
|
||||
# Stocke les dfs
|
||||
inscriptions_dfs[sxtag_id] = inscription_df
|
||||
notes_dfs[sxtag_id] = notes_df
|
||||
coeffs_dfs[sxtag_id] = coeffs_df
|
||||
|
||||
"""Réunit les inscriptions sous forme d'un cube etudids x competences x semestres"""
|
||||
sxtag_x_etudids_x_comps = [
|
||||
@ -278,26 +344,7 @@ class RCSemXTag(pe_tabletags.TableTag):
|
||||
sxtag_x_etudids_x_comps, axis=-1
|
||||
)
|
||||
|
||||
"""Réunit les notes sous forme d'un cube etudids x competences x semestres"""
|
||||
sxtag_x_etudids_x_comps = [
|
||||
notes_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
|
||||
]
|
||||
notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
|
||||
|
||||
"""Réunit les coeffs sous forme d'un cube etudids x competences x semestres"""
|
||||
sxtag_x_etudids_x_comps = [
|
||||
coeffs_dfs[sxtag_id] for sxtag_id in self.sxstags_aggreges
|
||||
]
|
||||
coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
|
||||
|
||||
return (
|
||||
inscriptions_dfs,
|
||||
inscriptions_etudids_x_comps_x_sxtag,
|
||||
notes_dfs,
|
||||
notes_etudids_x_comps_x_sxtag,
|
||||
coeffs_dfs,
|
||||
coeffs_etudids_x_comps_x_sxtag,
|
||||
)
|
||||
return inscriptions_dfs, inscriptions_etudids_x_comps_x_sxtag
|
||||
|
||||
def _do_taglist(self) -> list[str]:
|
||||
"""Synthétise les tags à partir des Sxtags aggrégés.
|
||||
@ -323,9 +370,7 @@ class RCSemXTag(pe_tabletags.TableTag):
|
||||
dict_competences |= sxtag.acronymes_ues_to_competences
|
||||
return dict_competences
|
||||
|
||||
def compute_notes_et_coeffs_competences(
|
||||
self, notes_cube: np.array, coeffs_cube: np.array, inscr_mask: np.array
|
||||
):
|
||||
def compute_notes_competences(self, set_cube: np.array, inscriptions: np.array):
|
||||
"""Calcule la moyenne par compétences (à un tag donné) sur plusieurs semestres (partant du set_cube).
|
||||
|
||||
La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles
|
||||
@ -334,11 +379,9 @@ class RCSemXTag(pe_tabletags.TableTag):
|
||||
par aggrégat de plusieurs semestres.
|
||||
|
||||
Args:
|
||||
notes_cube: notes moyennes aux compétences ndarray
|
||||
set_cube: notes moyennes aux compétences ndarray
|
||||
(etuds x UEs|compétences x sxtags), des floats avec des NaN
|
||||
coeffs_cube: coeffs appliqués aux compétences
|
||||
(etuds x UEs|compétences x sxtags), des floats avec des NaN
|
||||
inscr_mask: inscrptions aux compétences ndarray
|
||||
inscriptions: inscrptions aux compétences ndarray
|
||||
(etuds x UEs|compétences x sxtags), des 0 et des 1
|
||||
Returns:
|
||||
Un DataFrame avec pour columns les moyennes par tags,
|
||||
@ -346,45 +389,78 @@ class RCSemXTag(pe_tabletags.TableTag):
|
||||
"""
|
||||
# etudids_sorted: liste des étudiants (dim. 0 du cube)
|
||||
# competences_sorted: list (dim. 1 du cube)
|
||||
nb_etuds, nb_comps, nb_semestres = notes_cube.shape
|
||||
nb_etuds, nb_comps, nb_semestres = set_cube.shape
|
||||
# assert nb_etuds == len(etudids_sorted)
|
||||
# assert nb_comps == len(competences_sorted)
|
||||
|
||||
# Applique le masque d'inscriptions aux notes et aux coeffs
|
||||
notes_significatives = notes_cube * inscr_mask
|
||||
coeffs_significatifs = coeffs_cube * inscr_mask
|
||||
# Applique le masque d'inscriptions
|
||||
set_cube_significatif = set_cube * inscriptions
|
||||
|
||||
# Enlève les NaN des cubes pour les entrées manquantes
|
||||
notes_no_nan = np.nan_to_num(notes_significatives, nan=0.0)
|
||||
coeffs_no_nan = np.nan_to_num(coeffs_significatifs, nan=0.0)
|
||||
# Quelles entrées du cube contiennent des notes ?
|
||||
mask = ~np.isnan(set_cube_significatif)
|
||||
|
||||
# Enlève les NaN du cube de notes pour les entrées manquantes
|
||||
set_cube_no_nan = np.nan_to_num(set_cube_significatif, nan=0.0)
|
||||
|
||||
# Les moyennes par tag
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
mask = ~np.isnan(
|
||||
notes_significatives
|
||||
) # Quelles entrées contiennent des notes ?
|
||||
etud_moy_tag = np.sum(notes_no_nan, axis=2) / np.sum(mask, axis=2)
|
||||
|
||||
coeffs_pris_en_compte = coeffs_no_nan * mask
|
||||
coeff_tag = np.sum(coeffs_pris_en_compte, axis=2)
|
||||
|
||||
inscr_prise_en_compte = inscr_mask * mask
|
||||
inscr_prise_en_compte = np.nan_to_num(inscr_prise_en_compte, nan=-1.0)
|
||||
inscr_tag = np.max(inscr_prise_en_compte, axis=2)
|
||||
inscr_tag[inscr_tag < 0] = np.NaN # fix les max non calculés (-1) -> Na?
|
||||
etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2)
|
||||
|
||||
# Le dataFrame des notes moyennes
|
||||
etud_moy_tag = etud_moy_tag * inscr_tag
|
||||
etud_moy_tag_df = pd.DataFrame(
|
||||
etud_moy_tag,
|
||||
index=self.etudids_sorted, # les etudids
|
||||
columns=self.competences_sorted, # les competences
|
||||
)
|
||||
etud_moy_tag_df.fillna(np.nan)
|
||||
|
||||
return etud_moy_tag_df
|
||||
|
||||
def compute_coeffs_competences(
|
||||
self,
|
||||
coeff_cube: np.array,
|
||||
inscriptions: np.array,
|
||||
set_cube: np.array,
|
||||
):
|
||||
"""Calcule les coeffs à utiliser pour la moyenne générale (toutes compétences
|
||||
confondues), en fonction des inscriptions.
|
||||
|
||||
Args:
|
||||
coeffs_cube: coeffs impliqués dans la moyenne générale (semestres par semestres)
|
||||
inscriptions: inscriptions aux UES|Compétences ndarray
|
||||
(etuds x UEs|compétences x sxtags), des 0 ou des 1
|
||||
set_cube: les notes
|
||||
|
||||
|
||||
Returns:
|
||||
Un DataFrame de coefficients (etudids_sorted x compétences_sorted)
|
||||
"""
|
||||
# etudids_sorted: liste des étudiants (dim. 0 du cube)
|
||||
# competences_sorted: list (dim. 1 du cube)
|
||||
nb_etuds, nb_comps, nb_semestres = inscriptions.shape
|
||||
# assert nb_etuds == len(etudids_sorted)
|
||||
# assert nb_comps == len(competences_sorted)
|
||||
|
||||
# Applique le masque des inscriptions aux coeffs et aux notes
|
||||
coeffs_significatifs = coeff_cube * inscriptions
|
||||
|
||||
# Enlève les NaN du cube de notes pour les entrées manquantes
|
||||
coeffs_cube_no_nan = np.nan_to_num(coeffs_significatifs, nan=0.0)
|
||||
|
||||
# Quelles entrées du cube contiennent des notes ?
|
||||
mask = ~np.isnan(set_cube)
|
||||
|
||||
# Retire les coefficients associés à des données sans notes
|
||||
coeffs_cube_no_nan = coeffs_cube_no_nan * mask
|
||||
|
||||
# Somme les coefficients (correspondant à des notes)
|
||||
coeff_tag = np.sum(coeffs_cube_no_nan, axis=2)
|
||||
|
||||
# Le dataFrame des coeffs
|
||||
coeff_tag = coeff_tag * inscr_tag # Réapplique le masque des inscriptions
|
||||
coeffs_df = pd.DataFrame(
|
||||
coeff_tag, index=self.etudids_sorted, columns=self.competences_sorted
|
||||
)
|
||||
# Remet à Nan les coeffs à 0
|
||||
coeffs_df = coeffs_df.fillna(np.nan)
|
||||
|
||||
return etud_moy_tag_df, coeffs_df
|
||||
return coeffs_df
|
||||
|
@ -98,10 +98,8 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
self.parcours += [None]
|
||||
|
||||
# Les UEs en fonction des parcours
|
||||
self.ues_inscr_parcours_df = (
|
||||
self.load_ues_inscr_parcours()
|
||||
) # peut contenir du sport
|
||||
"""Inscription des étudiants aux UEs des parcours (etudids x ue_ids)"""
|
||||
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
|
||||
"""Inscription des étudiants aux UEs des parcours"""
|
||||
|
||||
# Les acronymes des UEs
|
||||
self.ues_to_acronymes = {ue.id: ue.acronyme for ue in self.ues_standards}
|
||||
@ -146,12 +144,6 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
f"--> Moyenne générale calculée avec pour coeffs d'UEs : {profils_aff}"
|
||||
)
|
||||
|
||||
# Les inscriptions aux acronymes d'ues
|
||||
self.acro_ues_inscr_parcours = self._get_acro_ues_inscr_parcours(
|
||||
self.ues_inscr_parcours_df, self.ues_standards
|
||||
)
|
||||
"""DataFrame indiquant à quelles UEs (données par leurs acronymes) sont inscrits les étudiants)"""
|
||||
|
||||
# Les capitalisations (mask etuids x acronyme_ue valant True si capitalisée, False sinon)
|
||||
self.capitalisations = self._get_capitalisations(self.ues_standards)
|
||||
"""DataFrame indiquant les UEs capitalisables d'un étudiant (etudids x )"""
|
||||
@ -175,7 +167,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
)
|
||||
|
||||
# Ajoute les moyennes par UEs + la moyenne générale (but)
|
||||
moy_gen = self.compute_moy_gen(self.acro_ues_inscr_parcours)
|
||||
moy_gen = self.compute_moy_gen()
|
||||
self.moyennes_tags["but"] = pe_moytag.MoyennesTag(
|
||||
"but",
|
||||
pe_moytag.CODE_MOY_UE,
|
||||
@ -248,31 +240,6 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
matrice_coeffs_moy_gen = matrice_coeffs_moy_gen.sort_index(axis=1)
|
||||
return matrice_coeffs_moy_gen
|
||||
|
||||
def _get_acro_ues_inscr_parcours(
|
||||
self, ues_inscr_parcours_df: pd.DataFrame, ues_standards: list[UniteEns]
|
||||
) -> pd.DataFrame:
|
||||
"""Renvoie un dataFrame donnant les inscriptions (Nan ou 1) des
|
||||
étudiants aux UEs définies par leur acronyme, en fonction de leur parcours
|
||||
(cf. ues_inscr_parcours_df) et en limitant les données aux UEs standards (hors sport=
|
||||
|
||||
Args:
|
||||
ues_inscr_parcours_df: Les inscriptions des étudiants aux UEs
|
||||
ues_standards: Les UEs standards à prendre en compte
|
||||
|
||||
Returns:
|
||||
Un dataFrame etudids x acronymes_UEs avec les coeffs des UEs
|
||||
"""
|
||||
matrice_inscription = ues_inscr_parcours_df * [
|
||||
1 for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé
|
||||
]
|
||||
matrice_inscription.columns = [
|
||||
self.ues_to_acronymes[ue.id] for ue in ues_standards
|
||||
]
|
||||
# Tri par etudids (dim 0) et par acronymes (dim 1)
|
||||
matrice_inscription = matrice_inscription.sort_index()
|
||||
matrice_inscription = matrice_inscription.sort_index(axis=1)
|
||||
return matrice_inscription
|
||||
|
||||
def _get_capitalisations(self, ues_standards) -> pd.DataFrame:
|
||||
"""Renvoie un dataFrame résumant les UEs capitalisables par les
|
||||
étudiants, d'après les décisions de jury (sous réserve qu'elles existent).
|
||||
@ -375,9 +342,6 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
colonnes = [ue.id for ue in self.ues_standards]
|
||||
moyennes_ues_tag = moyennes_ues_tag[colonnes]
|
||||
|
||||
# Met à zéro les moyennes non calculées/calculables
|
||||
moyennes_ues_tag.fillna(0.0, inplace=True)
|
||||
|
||||
# Applique le masque d'inscription aux UE pour ne conserver que les UE dans lequel l'étudiant est inscrit
|
||||
moyennes_ues_tag = moyennes_ues_tag[colonnes] * ues_inscr_parcours_df[colonnes]
|
||||
|
||||
@ -391,7 +355,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
|
||||
return moyennes_ues_tag
|
||||
|
||||
def compute_moy_gen(self, acro_ues_inscr_parcours):
|
||||
def compute_moy_gen(self):
|
||||
"""Récupère les moyennes des UEs pour le calcul de la moyenne générale,
|
||||
en associant à chaque UE.id son acronyme (toutes UEs confondues)
|
||||
"""
|
||||
@ -404,12 +368,6 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
|
||||
acronymes = [self.ues_to_acronymes[col] for col in colonnes]
|
||||
df_ues.columns = acronymes
|
||||
|
||||
# Met à zéro les moyennes non calculées/calculables
|
||||
df_ues.fillna(0.0, inplace=True)
|
||||
|
||||
# Réapplique le mask d'inscription
|
||||
df_ues = df_ues * acro_ues_inscr_parcours
|
||||
|
||||
# Tri par ordre aphabétique de colonnes
|
||||
df_ues.sort_index(axis=1)
|
||||
|
||||
|
@ -141,10 +141,6 @@ class SxTag(pe_tabletags.TableTag):
|
||||
aff = pe_affichage.repr_asso_ue_comp(self.acronymes_ues_to_competences)
|
||||
pe_affichage.pe_print(f"--> UEs/Compétences : {aff}")
|
||||
|
||||
# Les inscriptions des étudiants aux UEs (donnée par leur acronyme)
|
||||
# par report de celle du ressemfinal
|
||||
self.acro_ues_inscr_parcours = self.ressembuttag_final.acro_ues_inscr_parcours
|
||||
|
||||
# Les coeffs pour la moyenne générale (traduisant également l'inscription
|
||||
# des étudiants aux UEs) (etudids_sorted x acronymes_ues_sorted)
|
||||
self.matrice_coeffs_moy_gen = self.ressembuttag_final.matrice_coeffs_moy_gen
|
||||
@ -182,7 +178,7 @@ class SxTag(pe_tabletags.TableTag):
|
||||
pe_affichage.pe_print(f" > MoyTag 👜{tag}")
|
||||
|
||||
# Masque des inscriptions aux UEs (extraits de la matrice de coefficients)
|
||||
# inscr_mask: np.array = ~np.isnan(self.matrice_coeffs_moy_gen.to_numpy())
|
||||
inscr_mask: np.array = ~np.isnan(self.matrice_coeffs_moy_gen.to_numpy())
|
||||
|
||||
# Moyennes (tous modules confondus)
|
||||
if not self.has_notes_tag(tag):
|
||||
@ -198,7 +194,6 @@ class SxTag(pe_tabletags.TableTag):
|
||||
notes_df_gen, notes_cube_gen = self.compute_notes_ues_cube(tag)
|
||||
|
||||
# DataFrame des moyennes (tous modules confondus)
|
||||
inscr_mask = self.acro_ues_inscr_parcours.to_numpy()
|
||||
matrice_moys_ues = self.compute_notes_ues(
|
||||
notes_cube_gen, masque_cube, inscr_mask
|
||||
)
|
||||
@ -294,7 +289,7 @@ class SxTag(pe_tabletags.TableTag):
|
||||
def compute_notes_ues(
|
||||
self,
|
||||
set_cube: np.array,
|
||||
cap_mask_3D: np.array,
|
||||
masque_cube: np.array,
|
||||
inscr_mask: np.array,
|
||||
) -> pd.DataFrame:
|
||||
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
|
||||
@ -303,8 +298,7 @@ class SxTag(pe_tabletags.TableTag):
|
||||
Args:
|
||||
set_cube: notes moyennes aux modules ndarray
|
||||
(semestre_ids x etudids x UEs), des floats avec des NaN
|
||||
cap_mask_3D
|
||||
: masque indiquant si la note doit être prise en compte ndarray
|
||||
masque_cube: masque indiquant si la note doit être prise en compte ndarray
|
||||
(semestre_ids x etudids x UEs), des 1.0 ou des 0.0
|
||||
inscr_mask: masque etudids x UE traduisant les inscriptions des
|
||||
étudiants aux UE (du semestre terminal)
|
||||
@ -326,7 +320,10 @@ class SxTag(pe_tabletags.TableTag):
|
||||
set_cube = set_cube * inscr_mask_3D
|
||||
|
||||
# Entrées à garder en fonction des UEs capitalisées ou non
|
||||
set_cube = set_cube * cap_mask_3D
|
||||
set_cube = set_cube * masque_cube
|
||||
|
||||
# Quelles entrées du cube contiennent des notes ?
|
||||
mask = ~np.isnan(set_cube)
|
||||
|
||||
# Enlève les NaN du cube pour les entrées manquantes : NaN -> -1.0
|
||||
set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0)
|
||||
@ -335,12 +332,9 @@ class SxTag(pe_tabletags.TableTag):
|
||||
# TODO: Pour l'instant un max sans prise en compte des UE capitalisées
|
||||
etud_moy = np.max(set_cube_no_nan, axis=2)
|
||||
|
||||
# Fix les max non calculés (-1) -> NaN
|
||||
# Fix les max non calculé -1 -> NaN
|
||||
etud_moy[etud_moy < 0] = np.NaN
|
||||
|
||||
# Réapplique le masque d'inscription (dans le doute)
|
||||
etud_moy = etud_moy * inscr_mask
|
||||
|
||||
# Le dataFrame
|
||||
etud_moy_tag_df = pd.DataFrame(
|
||||
etud_moy,
|
||||
|
@ -9,7 +9,6 @@
|
||||
from flask import g
|
||||
from app import log
|
||||
from app.pe.rcss import pe_rcs
|
||||
import app.pe.pe_comp as pe_comp
|
||||
|
||||
PE_DEBUG = False
|
||||
|
||||
@ -136,45 +135,23 @@ def aff_tags_par_categories(dict_tags):
|
||||
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
|
||||
return f"Tags automatiques {aff_tags_auto} (aucun tag personnalisé)"
|
||||
|
||||
|
||||
def repr_jeune(etudid, etudiants):
|
||||
"""Renvoie la représentation d'un étudiant"""
|
||||
etat = "⛔" if etudid in etudiants.abandons_ids else "✅"
|
||||
jeune = f"{etat} {etudiants.identites[etudid].nomprenom} (#{etudid})"
|
||||
return jeune
|
||||
# Affichage
|
||||
|
||||
|
||||
def aff_trajectoires_suivies_par_etudiants(etudiants):
|
||||
"""Affiche les trajectoires (regroupement de (form)semestres)
|
||||
amenant un étudiant du S1 à un semestre final,
|
||||
en regroupant les étudiants par profil de trajectoires"""
|
||||
|
||||
amenant un étudiant du S1 à un semestre final"""
|
||||
# Affichage pour debug
|
||||
etudiants_ids = etudiants.etudiants_ids
|
||||
jeunes = list(enumerate(etudiants_ids))
|
||||
|
||||
profils_traj = {}
|
||||
|
||||
for no_etud, etudid in jeunes:
|
||||
jeune = repr_jeune(etudid, etudiants)
|
||||
etat = "⛔" if etudid in etudiants.abandons_ids else "✅"
|
||||
|
||||
# La trajectoire du jeune
|
||||
pe_print(f"--> {etat} {etudiants.identites[etudid].nomprenom} (#{etudid}) :")
|
||||
trajectoires = etudiants.trajectoires[etudid]
|
||||
profil_traj = []
|
||||
for nom_rcs, rcs in trajectoires.items():
|
||||
if rcs:
|
||||
profil_traj += [f" > RCS ⏯️{nom_rcs}: {rcs.get_repr()}"]
|
||||
aff_profil_traj = "\n".join(profil_traj)
|
||||
if aff_profil_traj not in profils_traj:
|
||||
profils_traj[aff_profil_traj] = []
|
||||
|
||||
profils_traj[aff_profil_traj] += [jeune]
|
||||
|
||||
# Affichage final
|
||||
for profil, jeunes in profils_traj.items():
|
||||
pe_print(f"--> Trajectoire suivie par : ")
|
||||
pe_print("\n".join([" " + jeune for jeune in jeunes]))
|
||||
pe_print(profil)
|
||||
pe_print(f" > RCS ⏯️{nom_rcs}: {rcs.get_repr()}")
|
||||
|
||||
|
||||
def aff_semXs_suivis_par_etudiants(etudiants):
|
||||
@ -221,11 +198,13 @@ def aff_capitalisations(etuds, ressembuttags, fid_final, acronymes_sorted, masqu
|
||||
|
||||
def repr_comp_et_ues(acronymes_ues_to_competences):
|
||||
"""Affichage pour debug"""
|
||||
asso_comp_to_ues = pe_comp.asso_comp_to_accronymes(acronymes_ues_to_competences)
|
||||
aff_comp = []
|
||||
competences_sorted = sorted(asso_comp_to_ues.keys())
|
||||
competences_sorted = sorted(acronymes_ues_to_competences.keys())
|
||||
for comp in competences_sorted:
|
||||
liste = ["📍" + accro for accro in asso_comp_to_ues[comp]]
|
||||
liste = []
|
||||
for acro in acronymes_ues_to_competences:
|
||||
if acronymes_ues_to_competences[acro] == comp:
|
||||
liste += ["📍" + acro]
|
||||
aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"]
|
||||
return "\n".join(aff_comp)
|
||||
|
||||
|
@ -337,21 +337,3 @@ def get_dernier_semestre_en_date(semestres: dict[int, FormSemestre]) -> FormSeme
|
||||
dernier_semestre = semestres[fid]
|
||||
return dernier_semestre
|
||||
return None
|
||||
|
||||
|
||||
def asso_comp_to_accronymes(accro_ues_to_competences):
|
||||
"""Partant d'un dictionnaire ``{nom_ue: compétence}`` associant des
|
||||
accronymes d'UEs à des compétences, renvoie l'association d'une compétence
|
||||
à ou aux UEs qui l'adresse : ``{competence: [liste_nom_ue]}``
|
||||
|
||||
Args:
|
||||
accro_ues_to_competences: Dictionnaire ``{nom_ue: compétence}``
|
||||
Return:
|
||||
Le dictionnaire ``{competence: [liste_nom_ue]}``
|
||||
"""
|
||||
asso = {}
|
||||
for accro, comp in accro_ues_to_competences.items():
|
||||
if comp not in asso:
|
||||
asso[comp] = []
|
||||
asso[comp].append(accro)
|
||||
return asso
|
||||
|
@ -705,7 +705,7 @@ class JuryPE(object):
|
||||
tag, aggregat=aggregat, type_colonnes=False, options=self.options
|
||||
)
|
||||
if not df_groupe.empty:
|
||||
aff_aggregat += [aggregat + " (Groupe)"]
|
||||
aff_aggregat += [aggregat]
|
||||
df = df.join(df_groupe)
|
||||
|
||||
# Le dataframe du classement sur la promo
|
||||
@ -718,7 +718,7 @@ class JuryPE(object):
|
||||
)
|
||||
|
||||
if not df_promo.empty:
|
||||
aff_aggregat += [aggregat + " (Promo)"]
|
||||
aff_aggregat += [aggregat]
|
||||
df = df.join(df_promo)
|
||||
|
||||
if aff_aggregat:
|
||||
|
@ -15,7 +15,7 @@ from app.pe.rcss import pe_rcs, pe_trajectoires
|
||||
|
||||
|
||||
class RCSemX(pe_rcs.RCS):
|
||||
"""Modélise un regroupement cohérent de SemX (en regroupant
|
||||
"""Modélise un regroupement cohérent de SemX (en même regroupant
|
||||
des semestres Sx combinés pour former les résultats des étudiants
|
||||
au semestre de rang x) dans le but de synthétiser les résultats
|
||||
du S1 jusqu'au semestre final ciblé par le RCSemX (dépendant de l'aggrégat
|
||||
|
@ -175,9 +175,10 @@ def sidebar(etudid: int = None):
|
||||
inscription = etud.inscription_courante()
|
||||
if inscription:
|
||||
formsemestre = inscription.formsemestre
|
||||
nbabsnj, nbabsjust, _ = sco_assiduites.formsemestre_get_assiduites_count(
|
||||
nbabs, nbabsjust = sco_assiduites.formsemestre_get_assiduites_count(
|
||||
etudid, formsemestre
|
||||
)
|
||||
nbabsnj = nbabs - nbabsjust
|
||||
H.append(
|
||||
f"""<span title="absences du {
|
||||
formsemestre.date_debut.strftime("%d/%m/%Y")
|
||||
@ -185,7 +186,7 @@ def sidebar(etudid: int = None):
|
||||
formsemestre.date_fin.strftime("%d/%m/%Y")
|
||||
}">({
|
||||
sco_preferences.get_preference("assi_metrique", None)})
|
||||
<br>{nbabsjust:1g} J., {nbabsnj:1g} N.J.</span>"""
|
||||
<br>{nbabsjust:1.0f} J., {nbabsnj:1.0f} N.J.</span>"""
|
||||
)
|
||||
H.append("<ul>")
|
||||
if current_user.has_permission(Permission.AbsChange):
|
||||
|
@ -67,7 +67,7 @@ def abs_notify(etudid: int, date: str | datetime.datetime):
|
||||
if not formsemestre:
|
||||
return # non inscrit a la date, pas de notification
|
||||
|
||||
_, nbabsjust, nbabs = sco_assiduites.get_assiduites_count_in_interval(
|
||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count_in_interval(
|
||||
etudid,
|
||||
metrique=scu.translate_assiduites_metric(
|
||||
sco_preferences.get_preference(
|
||||
|
@ -671,7 +671,7 @@ def create_absence_billet(
|
||||
# Gestion du cache
|
||||
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]:
|
||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
||||
tuple (nb abs non justifiées, nb abs justifiées)
|
||||
Utilise un cache.
|
||||
"""
|
||||
metrique = sco_preferences.get_preference("assi_metrique", sem["formsemestre_id"])
|
||||
@ -687,17 +687,17 @@ def formsemestre_get_assiduites_count(
|
||||
etudid: int, formsemestre: FormSemestre, moduleimpl_id: int = None
|
||||
) -> tuple[int, int]:
|
||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
||||
tuple (nb abs non justifiées, nb abs justifiées)
|
||||
Utilise un cache.
|
||||
"""
|
||||
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
|
||||
return get_assiduites_count_in_interval(
|
||||
etudid,
|
||||
date_debut=scu.localize_datetime(
|
||||
datetime.combine(formsemestre.date_debut, time(0, 0))
|
||||
datetime.combine(formsemestre.date_debut, time(8, 0))
|
||||
),
|
||||
date_fin=scu.localize_datetime(
|
||||
datetime.combine(formsemestre.date_fin, time(23, 0))
|
||||
datetime.combine(formsemestre.date_fin, time(18, 0))
|
||||
),
|
||||
metrique=scu.translate_assiduites_metric(metrique),
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
@ -714,12 +714,12 @@ def get_assiduites_count_in_interval(
|
||||
moduleimpl_id: int = None,
|
||||
):
|
||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
||||
tuple (nb abs, nb abs justifiées)
|
||||
On peut spécifier les dates comme datetime ou iso.
|
||||
Utilise un cache.
|
||||
"""
|
||||
date_debut_iso = date_debut_iso or date_debut.strftime("%Y-%m-%d")
|
||||
date_fin_iso = date_fin_iso or date_fin.strftime("%Y-%m-%d")
|
||||
date_debut_iso = date_debut_iso or date_debut.isoformat()
|
||||
date_fin_iso = date_fin_iso or date_fin.isoformat()
|
||||
key = f"{etudid}_{date_debut_iso}_{date_fin_iso}_assiduites"
|
||||
|
||||
r = sco_cache.AbsSemEtudCache.get(key)
|
||||
@ -744,10 +744,9 @@ def get_assiduites_count_in_interval(
|
||||
if not ans:
|
||||
log("warning: get_assiduites_count failed to cache")
|
||||
|
||||
nb_abs: int = r["absent"][metrique]
|
||||
nb_abs_nj: int = r["absent_non_just"][metrique]
|
||||
nb_abs_just: int = r["absent_just"][metrique]
|
||||
return (nb_abs_nj, nb_abs_just, nb_abs)
|
||||
nb_abs: dict = r["absent"][metrique]
|
||||
nb_abs_just: dict = r["absent_just"][metrique]
|
||||
return (nb_abs, nb_abs_just)
|
||||
|
||||
|
||||
def invalidate_assiduites_count(etudid: int, sem: dict):
|
||||
|
@ -196,7 +196,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
pid = partition["partition_id"]
|
||||
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
||||
# --- Absences
|
||||
_, I["nbabsjust"], I["nbabs"] = sco_assiduites.get_assiduites_count(etudid, nt.sem)
|
||||
I["nbabs"], I["nbabsjust"] = sco_assiduites.get_assiduites_count(etudid, nt.sem)
|
||||
|
||||
# --- Decision Jury
|
||||
infos, dpv = etud_descr_situation_semestre(
|
||||
@ -471,7 +471,7 @@ def _ue_mod_bulletin(
|
||||
) # peut etre 'NI'
|
||||
is_malus = mod["module"]["module_type"] == ModuleType.MALUS
|
||||
if bul_show_abs_modules:
|
||||
_, nbabsjust, nbabs = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||
mod_abs = [nbabs, nbabsjust]
|
||||
mod["mod_abs_txt"] = scu.fmt_abs(mod_abs)
|
||||
else:
|
||||
|
@ -296,7 +296,7 @@ def formsemestre_bulletinetud_published_dict(
|
||||
|
||||
# --- Absences
|
||||
if prefs["bul_show_abs"]:
|
||||
_, nbabsjust, nbabs = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
|
||||
|
||||
# --- Décision Jury
|
||||
|
@ -260,7 +260,7 @@ def make_xml_formsemestre_bulletinetud(
|
||||
numero=str(mod["numero"]),
|
||||
titre=quote_xml_attr(mod["titre"]),
|
||||
abbrev=quote_xml_attr(mod["abbrev"]),
|
||||
code_apogee=quote_xml_attr(mod["code_apogee"]),
|
||||
code_apogee=quote_xml_attr(mod["code_apogee"])
|
||||
# ects=ects ects des modules maintenant inutilisés
|
||||
)
|
||||
x_ue.append(x_mod)
|
||||
@ -347,7 +347,7 @@ def make_xml_formsemestre_bulletinetud(
|
||||
|
||||
# --- Absences
|
||||
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
|
||||
_, nbabsjust, nbabs = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
|
||||
# --- Decision Jury
|
||||
if (
|
||||
|
@ -722,8 +722,8 @@ def formsemestre_recap_parcours_table(
|
||||
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
|
||||
)
|
||||
# Absences (nb d'abs non just. dans ce semestre)
|
||||
nbabsnj = sco_assiduites.get_assiduites_count(etudid, sem)[0]
|
||||
H.append(f"""<td class="rcp_abs">{nbabsnj}</td>""")
|
||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||
H.append(f"""<td class="rcp_abs">{nbabs - nbabsjust}</td>""")
|
||||
|
||||
# UEs
|
||||
for ue in ues:
|
||||
|
@ -105,9 +105,7 @@ def etud_get_poursuite_info(sem: dict, etud: dict) -> dict:
|
||||
rangs.append(["rang_" + code_module, rang_module])
|
||||
|
||||
# Absences
|
||||
nbabsnj, nbabsjust, _ = sco_assiduites.get_assiduites_count(
|
||||
etudid, nt.sem
|
||||
)
|
||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, nt.sem)
|
||||
# En BUT, prend tout, sinon ne prend que les semestre validés par le jury
|
||||
if nt.is_apc or (
|
||||
dec
|
||||
@ -127,7 +125,7 @@ def etud_get_poursuite_info(sem: dict, etud: dict) -> dict:
|
||||
("date_debut", s["date_debut"]),
|
||||
("date_fin", s["date_fin"]),
|
||||
("periode", "%s - %s" % (s["mois_debut"], s["mois_fin"])),
|
||||
("AbsNonJust", nbabsnj),
|
||||
("AbsNonJust", nbabs - nbabsjust),
|
||||
("AbsJust", nbabsjust),
|
||||
]
|
||||
# ajout des 2 champs notes des modules et classement dans chaque module
|
||||
|
@ -620,7 +620,7 @@ class RowRecap(tb.Row):
|
||||
def add_abs(self):
|
||||
"Ajoute les colonnes absences"
|
||||
# Absences (nb d'abs non just. dans ce semestre)
|
||||
_, nbabsjust, nbabs = self.table.res.formsemestre.get_abs_count(self.etud.id)
|
||||
nbabs, nbabsjust = self.table.res.formsemestre.get_abs_count(self.etud.id)
|
||||
self.add_cell("nbabs", "Abs", f"{nbabs:1.0f}", "abs", raw_content=nbabs)
|
||||
self.add_cell(
|
||||
"nbabsjust", "Just.", f"{nbabsjust:1.0f}", "abs", raw_content=nbabsjust
|
||||
|
@ -154,25 +154,50 @@ div.submit > input {
|
||||
{% include "sco_timepicker.j2" %}
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Suppression d'un fichier justificatif
|
||||
function delete_file(justif_id, fileName, liElement) {
|
||||
// Construct the URL
|
||||
var url = "{{url_for('apiweb.justif_remove', justif_id=-1, scodoc_dept=g.scodoc_dept)}}".replace('-1', justif_id);
|
||||
|
||||
payload = {
|
||||
"remove": "list",
|
||||
"filenames" : [ fileName ],
|
||||
}
|
||||
// Send API request
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
// Hide the <li> element on successful deletion
|
||||
liElement.style.display = 'none';
|
||||
sco_message("fichier supprimé");
|
||||
} else {
|
||||
// Handle non-successful responses here
|
||||
console.error('Deletion failed:', response.statusText);
|
||||
sco_error_message("erreur lors de la suppression du fichier");
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
sco_error_message("erreur lors de la suppression du fichier (2)");
|
||||
});
|
||||
}
|
||||
|
||||
// Add event listeners to all elements with class 'suppr_fichier_just'
|
||||
var deleteButtons = document.querySelectorAll('.suppr_fichier_just');
|
||||
const form = document.getElementById('ajout-justificatif-etud');
|
||||
deleteButtons.forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
// Get the text content of the next sibling node
|
||||
var justif_id = this.dataset.justif_id;
|
||||
var fileName = this.nextSibling.nodeValue.trim();
|
||||
var liElement = this.parentNode; // Get the parent <li> element
|
||||
|
||||
// Create a hidden input element to store the file name
|
||||
var input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'suppr_fichier_just';
|
||||
input.value = fileName;
|
||||
form.appendChild(input);
|
||||
|
||||
liElement.remove();
|
||||
|
||||
delete_file(justif_id, fileName, liElement);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -174,7 +174,7 @@
|
||||
}
|
||||
|
||||
window.addEventListener('load', ()=>{
|
||||
const table_columns = [...document.querySelectorAll('th.external-sort')];
|
||||
const table_columns = [...document.querySelectorAll('.external-sort')];
|
||||
table_columns.forEach((e)=>e.addEventListener('click', ()=>{
|
||||
|
||||
// récupération de l'ordre "ascending" / "descending"
|
||||
|
@ -56,9 +56,9 @@
|
||||
</h2>
|
||||
<b>Absences</b>
|
||||
{% if sco.etud_cur_sem %}
|
||||
<span title="absences du {{ sco.etud_cur_sem['date_debut'].strftime('%d/%m/%Y') }}
|
||||
au {{ sco.etud_cur_sem['date_fin'].strftime('%d/%m/%Y') }}">({{sco.prefs["assi_metrique"]}})
|
||||
<br />{{'%1g'|format(sco.nbabsjust)}} J., {{'%1g'|format(sco.nbabsnj)}} N.J.</span>
|
||||
<span title="absences du {{ sco.etud_cur_sem['date_debut'] }}
|
||||
au {{ sco.etud_cur_sem['date_fin'] }}">({{sco.prefs["assi_metrique"]}})
|
||||
<br />{{'%1.0f'|format(sco.nbabsjust)}} J., {{'%1.0f'|format(sco.nbabsnj)}} N.J.</span>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% if current_user.has_permission(sco.Permission.AbsChange) %}
|
||||
|
@ -74,9 +74,8 @@ class ScoData:
|
||||
if ins:
|
||||
self.etud_cur_sem = ins.formsemestre
|
||||
(
|
||||
self.nbabsnj,
|
||||
self.nbabsjust,
|
||||
self.nbabs,
|
||||
self.nbabsjust,
|
||||
) = sco_assiduites.get_assiduites_count_in_interval(
|
||||
etud.id,
|
||||
self.etud_cur_sem.date_debut.isoformat(),
|
||||
@ -85,6 +84,7 @@ class ScoData:
|
||||
sco_preferences.get_preference("assi_metrique")
|
||||
),
|
||||
)
|
||||
self.nbabsnj = self.nbabs - self.nbabsjust
|
||||
else:
|
||||
self.etud_cur_sem = None
|
||||
else:
|
||||
|
@ -658,8 +658,6 @@ def edit_justificatif_etud(justif_id: int):
|
||||
etudid=justif.etudiant.id,
|
||||
)
|
||||
if form.validate_on_submit():
|
||||
if form.cancel.data: # cancel button
|
||||
return redirect(redirect_url)
|
||||
if _record_justificatif_etud(justif.etudiant, form, justif):
|
||||
return redirect(redirect_url)
|
||||
|
||||
@ -759,6 +757,7 @@ def _record_justificatif_etud(
|
||||
dt_fin_tz_server,
|
||||
dt_entry_date_tz_server,
|
||||
) = _get_dates_from_assi_form(form, all_day=True)
|
||||
|
||||
if not ok:
|
||||
log("_record_justificatif_etud: dates invalides")
|
||||
form.set_error("Erreur: dates invalides")
|
||||
@ -794,14 +793,6 @@ def _record_justificatif_etud(
|
||||
)
|
||||
else:
|
||||
message = "Pas de modification"
|
||||
fichier_suppr: list[str] = request.form.getlist("suppr_fichier_just")
|
||||
|
||||
if len(fichier_suppr) > 0 and justif.fichier is not None:
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
for fichier in fichier_suppr:
|
||||
archiver.delete_justificatif(etud, justif.fichier, fichier)
|
||||
flash(f"Fichier {fichier} supprimé")
|
||||
|
||||
else:
|
||||
justif = Justificatif.create_justificatif(
|
||||
etud,
|
||||
|
@ -1187,18 +1187,14 @@ def view_module_abs(moduleimpl_id, fmt="html"):
|
||||
|
||||
rows = []
|
||||
for etud in inscrits:
|
||||
(
|
||||
nb_abs_nj,
|
||||
nb_abs_just,
|
||||
nb_abs,
|
||||
) = sco_assiduites.formsemestre_get_assiduites_count(
|
||||
nb_abs, nb_abs_just = sco_assiduites.formsemestre_get_assiduites_count(
|
||||
etud.id, modimpl.formsemestre, moduleimpl_id=modimpl.id
|
||||
)
|
||||
rows.append(
|
||||
{
|
||||
"nomprenom": etud.nomprenom,
|
||||
"just": nb_abs_just,
|
||||
"nojust": nb_abs_nj,
|
||||
"nojust": nb_abs - nb_abs_just,
|
||||
"total": nb_abs,
|
||||
"_nomprenom_target": url_for(
|
||||
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||
|
@ -72,6 +72,8 @@ def test_general(test_client):
|
||||
verifier_comptage_et_filtrage_assiduites(etuds, moduleimpls[:4], formsemestres)
|
||||
verifier_filtrage_justificatifs(etuds[0], justificatifs)
|
||||
|
||||
essais_cache(etuds[0].etudid, formsemestres[:2], moduleimpls)
|
||||
|
||||
editer_supprimer_assiduites(etuds, moduleimpls)
|
||||
editer_supprimer_justificatif(etuds[0])
|
||||
|
||||
@ -400,6 +402,54 @@ def _get_justi(
|
||||
).first()
|
||||
|
||||
|
||||
def essais_cache(etudid, sems: tuple[FormSemestre], moduleimpls: list[ModuleImpl]):
|
||||
"""Vérification des fonctionnalités du cache"""
|
||||
# TODO faire un test séparé du test_general
|
||||
# voir test_calcul_assiduites pour faire
|
||||
|
||||
date_deb: str = "2022-09-01T07:00"
|
||||
date_fin: str = "2023-01-31T19:00"
|
||||
|
||||
assiduites_count_no_cache = scass.get_assiduites_count_in_interval(
|
||||
etudid, date_deb, date_fin
|
||||
)
|
||||
assiduites_count_cache = scass.get_assiduites_count_in_interval(
|
||||
etudid, date_deb, date_fin
|
||||
)
|
||||
|
||||
assert (
|
||||
assiduites_count_cache == assiduites_count_no_cache == (2, 1)
|
||||
), "Erreur cache classique"
|
||||
|
||||
assert scass.formsemestre_get_assiduites_count(etudid, sems[0]) == (
|
||||
2,
|
||||
1,
|
||||
), "Erreur formsemestre_get_assiduites_count (sans module) A"
|
||||
assert scass.formsemestre_get_assiduites_count(etudid, sems[1]) == (
|
||||
0,
|
||||
0,
|
||||
), "Erreur formsemestre_get_assiduites_count (sans module) B"
|
||||
|
||||
assert scass.formsemestre_get_assiduites_count(
|
||||
etudid, sems[0], moduleimpl_id=moduleimpls[0].id
|
||||
) == (
|
||||
1,
|
||||
1,
|
||||
), "Erreur formsemestre_get_assiduites_count (avec module) A"
|
||||
assert scass.formsemestre_get_assiduites_count(
|
||||
etudid, sems[0], moduleimpl_id=moduleimpls[1].id
|
||||
) == (
|
||||
1,
|
||||
0,
|
||||
), "Erreur formsemestre_get_assiduites_count (avec module) A"
|
||||
assert scass.formsemestre_get_assiduites_count(
|
||||
etudid, sems[0], moduleimpl_id=moduleimpls[2].id
|
||||
) == (
|
||||
0,
|
||||
0,
|
||||
), "Erreur formsemestre_get_assiduites_count (avec module) A"
|
||||
|
||||
|
||||
def ajouter_justificatifs(etud):
|
||||
"""test de l'ajout des justificatifs"""
|
||||
|
||||
@ -1364,7 +1414,6 @@ def test_cas_justificatifs(test_client):
|
||||
Tests de certains cas particuliers des justificatifs
|
||||
- Création du justificatif avant ou après assiduité
|
||||
- Assiduité complétement couverte ou non
|
||||
- Modification de la couverture (edition du justificatif)
|
||||
"""
|
||||
|
||||
data = _setup_fake_db(
|
||||
@ -1454,279 +1503,3 @@ def test_cas_justificatifs(test_client):
|
||||
assert (
|
||||
len(scass.justifies(justif_4)) == 0
|
||||
), "Justification complète non prise en compte (c2)"
|
||||
|
||||
# <- Vérification modification de la couverture ->
|
||||
|
||||
# Deux assiduités, 8/01/2024 de 8h à 10h et 14h à 16h
|
||||
|
||||
assi_2: Assiduite = Assiduite.create_assiduite(
|
||||
etud=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-08T08:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-08T10:00", True),
|
||||
etat=scu.EtatAssiduite.ABSENT,
|
||||
)
|
||||
assi_3: Assiduite = Assiduite.create_assiduite(
|
||||
etud=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-08T14:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-08T16:00", True),
|
||||
etat=scu.EtatAssiduite.ABSENT,
|
||||
)
|
||||
|
||||
# <=>Justification complète<=>
|
||||
# les deux assiduités sont couvertes
|
||||
|
||||
justif_5: Justificatif = Justificatif.create_justificatif(
|
||||
etudiant=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-08T00:00:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-08T23:59:59", True),
|
||||
etat=scu.EtatJustificatif.VALIDE,
|
||||
)
|
||||
|
||||
# Justification des assiduités
|
||||
assi_ids: list[int] = justif_5.justifier_assiduites()
|
||||
assert len(assi_ids) == 2, "Vérification Modification couverture (d1)"
|
||||
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (d2)"
|
||||
assert assi_3.assiduite_id in assi_ids, "Vérification Modification couverture (d3)"
|
||||
assert assi_2.est_just is True, "Vérification Modification couverture (d4)"
|
||||
assert assi_3.est_just is True, "Vérification Modification couverture (d5)"
|
||||
|
||||
# Déjustification des assiduités
|
||||
justif_5.dejustifier_assiduites()
|
||||
assi_ids: list[int] = justif_5.dejustifier_assiduites()
|
||||
assert len(assi_ids) == 2, "Vérification Modification couverture (d6)"
|
||||
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (d7)"
|
||||
assert assi_3.assiduite_id in assi_ids, "Vérification Modification couverture (d8)"
|
||||
assert assi_2.est_just is False, "Vérification Modification couverture (d9)"
|
||||
assert assi_3.est_just is False, "Vérification Modification couverture (d10)"
|
||||
|
||||
# <=>Justification Partielle<=>
|
||||
# Seule la première assiduité est couverte
|
||||
|
||||
justif_5.date_fin = scu.is_iso_formated("2024-01-08T11:00", True)
|
||||
db.session.add(justif_5)
|
||||
db.session.commit()
|
||||
|
||||
assi_ids: list[int] = justif_5.justifier_assiduites()
|
||||
assert len(assi_ids) == 1, "Vérification Modification couverture (e1)"
|
||||
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (e2)"
|
||||
assert (
|
||||
assi_3.assiduite_id not in assi_ids
|
||||
), "Vérification Modification couverture (e3)"
|
||||
assert assi_2.est_just is True, "Vérification Modification couverture (e4)"
|
||||
assert assi_3.est_just is False, "Vérification Modification couverture (e5)"
|
||||
|
||||
assi_ids: list[int] = justif_5.dejustifier_assiduites()
|
||||
assert len(assi_ids) == 1, "Vérification Modification couverture (e6)"
|
||||
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (e7)"
|
||||
assert (
|
||||
assi_3.assiduite_id not in assi_ids
|
||||
), "Vérification Modification couverture (e3)"
|
||||
assert assi_2.est_just is False, "Vérification Modification couverture (e8)"
|
||||
assert assi_3.est_just is False, "Vérification Modification couverture (e9)"
|
||||
|
||||
# <=>Justification Multiple<=>
|
||||
# Deux justificatifs couvrent une même assiduité
|
||||
|
||||
# on justifie la première assiduité avec le premier justificatif
|
||||
justif_5.justifier_assiduites()
|
||||
|
||||
# deuxième justificatif
|
||||
justif_6: Justificatif = Justificatif.create_justificatif(
|
||||
etudiant=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-08T08:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-08T10:00", True),
|
||||
etat=scu.EtatJustificatif.VALIDE,
|
||||
)
|
||||
|
||||
assi_ids: list[int] = justif_6.justifier_assiduites()
|
||||
assert len(assi_ids) == 1, "Vérification Modification couverture (f1)"
|
||||
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (f2)"
|
||||
assert (
|
||||
assi_3.assiduite_id not in assi_ids
|
||||
), "Vérification Modification couverture (f3)"
|
||||
assert assi_2.est_just is True, "Vérification Modification couverture (f4)"
|
||||
assert assi_3.est_just is False, "Vérification Modification couverture (f5)"
|
||||
|
||||
# on déjustifie le justificatif 5
|
||||
justif_5.etat = scu.EtatJustificatif.NON_VALIDE
|
||||
db.session.add(justif_5)
|
||||
db.session.commit()
|
||||
|
||||
assi_ids: list[int] = justif_5.dejustifier_assiduites()
|
||||
assert len(assi_ids) == 0, "Vérification Modification couverture (f6)"
|
||||
assert (
|
||||
assi_2.assiduite_id not in assi_ids
|
||||
), "Vérification Modification couverture (f7)"
|
||||
assert assi_2.est_just is True, "Vérification Modification couverture (f8)"
|
||||
|
||||
# on déjustifie le justificatif 6
|
||||
justif_6.etat = scu.EtatJustificatif.NON_VALIDE
|
||||
db.session.add(justif_6)
|
||||
db.session.commit()
|
||||
assi_ids: list[int] = justif_6.dejustifier_assiduites()
|
||||
assert len(assi_ids) == 1, "Vérification Modification couverture (f9)"
|
||||
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (f10)"
|
||||
assert assi_2.est_just is False, "Vérification Modification couverture (f11)"
|
||||
|
||||
# <=>Justification Chevauchée<=>
|
||||
# 1 justificatif chevauche une assiduité (8h -> 10h) (9h -> 11h)
|
||||
|
||||
justif_7: Justificatif = Justificatif.create_justificatif(
|
||||
etudiant=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-08T09:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-08T11:00", True),
|
||||
etat=scu.EtatJustificatif.VALIDE,
|
||||
)
|
||||
|
||||
assi_ids: list[int] = justif_7.justifier_assiduites()
|
||||
assert len(assi_ids) == 0, "Vérification Modification couverture (g1)"
|
||||
assert (
|
||||
assi_2.assiduite_id not in assi_ids
|
||||
), "Vérification Modification couverture (g2)"
|
||||
assert assi_2.est_just is False, "Vérification Modification couverture (g3)"
|
||||
|
||||
# Modification pour correspondre à l'assiduité
|
||||
justif_7.date_debut = scu.is_iso_formated("2024-01-08T08:00", True)
|
||||
db.session.add(justif_7)
|
||||
db.session.commit()
|
||||
|
||||
assi_ids: list[int] = justif_7.justifier_assiduites()
|
||||
assert len(assi_ids) == 1, "Vérification Modification couverture (g4)"
|
||||
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (g5)"
|
||||
assert assi_2.est_just is True, "Vérification Modification couverture (g6)"
|
||||
|
||||
|
||||
def test_cache_assiduites(test_client):
|
||||
"""Vérification du bon fonctionnement du cache des assiduités"""
|
||||
|
||||
data = _setup_fake_db(
|
||||
[("2024-01-01", "2024-06-30"), ("2024-07-01", "2024-12-31")],
|
||||
1,
|
||||
1,
|
||||
)
|
||||
|
||||
formsemestre1: FormSemestre = data["formsemestres"][0]
|
||||
formsemestre2: FormSemestre = data["formsemestres"][1]
|
||||
|
||||
moduleimpl: ModuleImpl = data["moduleimpls"][0]
|
||||
etud: Identite = data["etuds"][0]
|
||||
|
||||
# Création des assiduités
|
||||
assiduites: list[dict] = [
|
||||
# Semestre 1
|
||||
{
|
||||
"date_debut": "2024-01-08T08:00",
|
||||
"date_fin": "2024-01-08T10:00",
|
||||
"moduleimpl": moduleimpl,
|
||||
},
|
||||
{
|
||||
"date_debut": "2024-01-08T14:00",
|
||||
"date_fin": "2024-01-08T16:00",
|
||||
"moduleimpl": moduleimpl,
|
||||
},
|
||||
{
|
||||
"date_debut": "2024-01-09T08:00",
|
||||
"date_fin": "2024-01-09T10:00",
|
||||
"moduleimpl": None,
|
||||
},
|
||||
{
|
||||
"date_debut": "2024-01-09T14:00",
|
||||
"date_fin": "2024-01-09T16:00",
|
||||
"moduleimpl": None,
|
||||
},
|
||||
{
|
||||
"date_debut": "2024-01-10T08:00",
|
||||
"date_fin": "2024-01-10T10:00",
|
||||
"moduleimpl": None,
|
||||
},
|
||||
{
|
||||
"date_debut": "2024-01-10T14:00",
|
||||
"date_fin": "2024-01-10T16:00",
|
||||
"moduleimpl": moduleimpl,
|
||||
},
|
||||
# Semestre 2
|
||||
{
|
||||
"date_debut": "2024-07-09T14:00",
|
||||
"date_fin": "2024-07-09T16:00",
|
||||
"moduleimpl": None,
|
||||
},
|
||||
{
|
||||
"date_debut": "2024-07-10T08:00",
|
||||
"date_fin": "2024-07-10T10:00",
|
||||
"moduleimpl": None,
|
||||
},
|
||||
{
|
||||
"date_debut": "2024-07-10T14:00",
|
||||
"date_fin": "2024-07-10T16:00",
|
||||
"moduleimpl": None,
|
||||
},
|
||||
]
|
||||
|
||||
justificatifs: list[dict] = [
|
||||
{
|
||||
"date_debut": "2024-01-10T00:00",
|
||||
"date_fin": "2024-01-10T23:59",
|
||||
},
|
||||
{
|
||||
"date_debut": "2024-07-09T00:00",
|
||||
"date_fin": "2024-07-09T23:59",
|
||||
},
|
||||
]
|
||||
|
||||
# On ajoute les assiduités et les justificatifs
|
||||
|
||||
for assi in assiduites:
|
||||
Assiduite.create_assiduite(
|
||||
etud=etud,
|
||||
date_debut=scu.is_iso_formated(assi["date_debut"], True),
|
||||
date_fin=scu.is_iso_formated(assi["date_fin"], True),
|
||||
moduleimpl=assi["moduleimpl"],
|
||||
etat=scu.EtatAssiduite.ABSENT,
|
||||
)
|
||||
|
||||
for justi in justificatifs:
|
||||
Justificatif.create_justificatif(
|
||||
etudiant=etud,
|
||||
date_debut=scu.is_iso_formated(justi["date_debut"], True),
|
||||
date_fin=scu.is_iso_formated(justi["date_fin"], True),
|
||||
etat=scu.EtatJustificatif.VALIDE,
|
||||
).justifier_assiduites()
|
||||
|
||||
# Premier semestre 4nj / 2j / 6t
|
||||
assert scass.get_assiduites_count(etud.id, formsemestre1.to_dict()) == (4, 2, 6)
|
||||
assert scass.formsemestre_get_assiduites_count(etud.id, formsemestre1) == (4, 2, 6)
|
||||
|
||||
# ModuleImpl 2nj / 1j / 3t
|
||||
assert scass.formsemestre_get_assiduites_count(
|
||||
etud.id, formsemestre1, moduleimpl.id
|
||||
) == (2, 1, 3)
|
||||
# Deuxième semestre 2nj / 1j / 3t
|
||||
assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
|
||||
|
||||
# On supprime la première assiduité (sans invalider le cache)
|
||||
assi: Assiduite = Assiduite.query.filter_by(etudid=etud.id).first()
|
||||
db.session.delete(assi)
|
||||
db.session.commit()
|
||||
|
||||
# Premier semestre 4nj / 2j / 6t (Identique car cache)
|
||||
assert scass.get_assiduites_count(etud.id, formsemestre1.to_dict()) == (4, 2, 6)
|
||||
assert scass.formsemestre_get_assiduites_count(etud.id, formsemestre1) == (4, 2, 6)
|
||||
# ModuleImpl 1nj / 1j / 2t (Change car non cache)
|
||||
assert scass.formsemestre_get_assiduites_count(
|
||||
etud.id, formsemestre1, moduleimpl.id
|
||||
) == (1, 1, 2)
|
||||
# Deuxième semestre 2nj / 1j / 3t (Identique car cache et non modifié)
|
||||
assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
|
||||
|
||||
# On invalide maintenant le cache
|
||||
scass.invalidate_assiduites_count(etud.id, formsemestre1.to_dict())
|
||||
|
||||
# Premier semestre 3nj / 2j / 5t (Change car cache invalidé)
|
||||
assert scass.get_assiduites_count(etud.id, formsemestre1.to_dict()) == (3, 2, 5)
|
||||
assert scass.formsemestre_get_assiduites_count(etud.id, formsemestre1) == (3, 2, 5)
|
||||
# ModuleImpl 1nj / 1j / 2t (Ne change pas car pas de changement)
|
||||
assert scass.formsemestre_get_assiduites_count(
|
||||
etud.id, formsemestre1, moduleimpl.id
|
||||
) == (1, 1, 2)
|
||||
# Deuxième semestre 2nj / 1j / 3t (Identique car cache et non modifié)
|
||||
assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
|
||||
|
@ -191,7 +191,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
etudid = etuds[0]["etudid"]
|
||||
|
||||
_signal_absences_justificatifs(etudid)
|
||||
_, nbabsjust, nbabs = scass.get_assiduites_count(etudid, sem)
|
||||
nbabs, nbabsjust = scass.get_assiduites_count(etudid, sem)
|
||||
assert nbabs == 6, f"incorrect nbabs ({nbabs})"
|
||||
assert nbabsjust == 2, f"incorrect nbabsjust ({nbabsjust})"
|
||||
|
||||
|
@ -396,7 +396,7 @@ def ajouter_assiduites_justificatifs(formsemestre: FormSemestre):
|
||||
|
||||
for etud in formsemestre.etuds:
|
||||
base_date = datetime.datetime(
|
||||
2021, 9, [6, 13, 20, 27][random.randint(0, 3)], 8, 0, 0
|
||||
2022, 9, [5, 12, 19, 26][random.randint(0, 3)], 8, 0, 0
|
||||
)
|
||||
base_date = localize_datetime(base_date)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user