forked from ScoDoc/ScoDoc
WIP: calcul unifié, bonus sport BUT
This commit is contained in:
parent
2b1a3ee95e
commit
8385941cf6
@ -13,6 +13,7 @@ from flask import url_for, g
|
|||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc import sco_bulletins_json
|
from app.scodoc import sco_bulletins_json
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
from app.scodoc.sco_utils import fmt_note
|
from app.scodoc.sco_utils import fmt_note
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
|
|
||||||
@ -49,23 +50,30 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
d = {
|
d = {
|
||||||
"id": ue.id,
|
"id": ue.id,
|
||||||
"numero": ue.numero,
|
"numero": ue.numero,
|
||||||
|
"type": ue.type,
|
||||||
"ECTS": {
|
"ECTS": {
|
||||||
"acquis": 0, # XXX TODO voir jury
|
"acquis": 0, # XXX TODO voir jury
|
||||||
"total": ue.ects,
|
"total": ue.ects,
|
||||||
},
|
},
|
||||||
|
"color": ue.color,
|
||||||
"competence": None, # XXX TODO lien avec référentiel
|
"competence": None, # XXX TODO lien avec référentiel
|
||||||
"moyenne": {
|
"moyenne": None,
|
||||||
"value": fmt_note(self.etud_moy_ue[ue.id][etud.id]),
|
# Le bonus sport appliqué sur cette UE
|
||||||
"min": fmt_note(self.etud_moy_ue[ue.id].min()),
|
"bonus": self.bonus_ues[ue.id][etud.id]
|
||||||
"max": fmt_note(self.etud_moy_ue[ue.id].max()),
|
if self.bonus_ues is not None and ue.id in self.bonus_ues
|
||||||
"moy": fmt_note(self.etud_moy_ue[ue.id].mean()),
|
else 0.0,
|
||||||
},
|
|
||||||
"bonus": None, # XXX TODO
|
|
||||||
"malus": None, # XXX TODO voir ce qui est ici
|
"malus": None, # XXX TODO voir ce qui est ici
|
||||||
"capitalise": None, # "AAAA-MM-JJ" TODO
|
"capitalise": None, # "AAAA-MM-JJ" TODO
|
||||||
"ressources": self.etud_ue_mod_results(etud, ue, self.ressources),
|
"ressources": self.etud_ue_mod_results(etud, ue, self.ressources),
|
||||||
"saes": self.etud_ue_mod_results(etud, ue, self.saes),
|
"saes": self.etud_ue_mod_results(etud, ue, self.saes),
|
||||||
}
|
}
|
||||||
|
if ue.type != UE_SPORT:
|
||||||
|
d["moyenne"] = {
|
||||||
|
"value": fmt_note(self.etud_moy_ue[ue.id][etud.id]),
|
||||||
|
"min": fmt_note(self.etud_moy_ue[ue.id].min()),
|
||||||
|
"max": fmt_note(self.etud_moy_ue[ue.id].max()),
|
||||||
|
"moy": fmt_note(self.etud_moy_ue[ue.id].mean()),
|
||||||
|
}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def etud_mods_results(self, etud, modimpls) -> dict:
|
def etud_mods_results(self, etud, modimpls) -> dict:
|
||||||
|
@ -134,8 +134,12 @@ def bulletin_but_xml_compat(
|
|||||||
moy=scu.fmt_note(results.etud_moy_gen.mean()), # moyenne des moy. gen.
|
moy=scu.fmt_note(results.etud_moy_gen.mean()), # moyenne des moy. gen.
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rang = 0 # XXX TODO rang de l'étduiant selon la moy gen indicative
|
rang = 0 # XXX TODO rang de l'étudiant selon la moy gen indicative
|
||||||
bonus = 0 # XXX TODO valeur du bonus sport
|
# valeur du bonus sport
|
||||||
|
if results.bonus is not None:
|
||||||
|
bonus = results.bonus[etud.id]
|
||||||
|
else:
|
||||||
|
bonus = 0
|
||||||
doc.append(Element("rang", value=str(rang), ninscrits=str(nb_inscrits)))
|
doc.append(Element("rang", value=str(rang), ninscrits=str(nb_inscrits)))
|
||||||
# XXX TODO: ajouter "rang_group" : rangs dans les partitions
|
# XXX TODO: ajouter "rang_group" : rangs dans les partitions
|
||||||
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
||||||
|
@ -19,7 +19,11 @@ class StatsMoyenne:
|
|||||||
def __init__(self, vals):
|
def __init__(self, vals):
|
||||||
"""Calcul les statistiques.
|
"""Calcul les statistiques.
|
||||||
Les valeurs NAN ou non numériques sont toujours enlevées.
|
Les valeurs NAN ou non numériques sont toujours enlevées.
|
||||||
|
Si vals is None, renvoie des zéros (utilisé pour UE bonus)
|
||||||
"""
|
"""
|
||||||
|
if vals is None:
|
||||||
|
self.moy = self.min = self.max = self.size = self.nb_vals = 0
|
||||||
|
else:
|
||||||
self.moy = np.nanmean(vals)
|
self.moy = np.nanmean(vals)
|
||||||
self.min = np.nanmin(vals)
|
self.min = np.nanmin(vals)
|
||||||
self.max = np.nanmax(vals)
|
self.max = np.nanmax(vals)
|
||||||
|
322
app/comp/bonus_spo.py
Normal file
322
app/comp/bonus_spo.py
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Classes spécifiques de calcul du bonus sport, culture ou autres activités
|
||||||
|
|
||||||
|
Les classes de Bonus fournissent deux méthodes:
|
||||||
|
- get_bonus_ues()
|
||||||
|
- get_bonus_moy_gen()
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from app import models
|
||||||
|
from app.models import UniteEns, Module, ModuleImpl, ModuleUECoef
|
||||||
|
from app.comp import moy_mod
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.scodoc import bonus_sport
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
|
def get_bonus_sport_class_from_name(dept_id):
|
||||||
|
"""La classe de bonus sport pour le département indiqué.
|
||||||
|
Note: en ScoDoc 9, le bonus sport est défini gloabelement et
|
||||||
|
ne dépend donc pas du département.
|
||||||
|
Résultat: une sous-classe de BonusSport
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class BonusSport:
|
||||||
|
"""Calcul du bonus sport.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- sem_modimpl_moys :
|
||||||
|
notes moyennes aux modules (tous les étuds x tous les modimpls)
|
||||||
|
floats avec des NaN.
|
||||||
|
En classique: sem_matrix, ndarray (etuds x modimpls)
|
||||||
|
En APC: sem_cube, ndarray (etuds x modimpls x UEs)
|
||||||
|
- ues: les ues du semestre (incluant le bonus sport)
|
||||||
|
- modimpl_inscr_df: matrice d'inscription aux modules du semestre (etud x modimpl)
|
||||||
|
- modimpl_coefs: les coefs des modules
|
||||||
|
En classique: 1d ndarray de float (modimpl)
|
||||||
|
En APC: 2d ndarray de float, (modimpl x UE) <= attention à transposer
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Si vrai, en APC, si le bonus UE est None, reporte le bonus moy gen:
|
||||||
|
apc_apply_bonus_mg_to_ues = True
|
||||||
|
# Attributs virtuels:
|
||||||
|
seuil_moy_gen = None
|
||||||
|
proportion_point = None
|
||||||
|
bonus_moy_gen_limit = None
|
||||||
|
|
||||||
|
name = "virtual"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
formsemestre: FormSemestre,
|
||||||
|
sem_modimpl_moys: np.array,
|
||||||
|
ues: list,
|
||||||
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
|
modimpl_coefs: np.array,
|
||||||
|
):
|
||||||
|
self.formsemestre = formsemestre
|
||||||
|
self.ues = ues
|
||||||
|
self.etuds_idx = modimpl_inscr_df.index # les étudiants inscrits au semestre
|
||||||
|
self.bonus_ues: pd.DataFrame = None # virtual
|
||||||
|
self.bonus_moy_gen: pd.Series = None # virtual
|
||||||
|
# Restreint aux modules standards des UE de type "sport":
|
||||||
|
modimpl_mask = np.array(
|
||||||
|
[
|
||||||
|
(m.module.module_type == ModuleType.STANDARD)
|
||||||
|
and (m.module.ue.type == UE_SPORT)
|
||||||
|
for m in formsemestre.modimpls_sorted
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.modimpls_spo = [
|
||||||
|
modimpl
|
||||||
|
for i, modimpl in enumerate(formsemestre.modimpls_sorted)
|
||||||
|
if modimpl_mask[i]
|
||||||
|
]
|
||||||
|
"liste des modimpls sport"
|
||||||
|
|
||||||
|
# Les moyennes des modules "sport": (une par UE en APC)
|
||||||
|
sem_modimpl_moys_spo = sem_modimpl_moys[:, modimpl_mask]
|
||||||
|
# Les inscriptions aux modules sport:
|
||||||
|
modimpl_inscr_spo = modimpl_inscr_df.values[:, modimpl_mask]
|
||||||
|
# Les coefficients des modules sport (en apc: nb_mod_sport x nb_ue)
|
||||||
|
modimpl_coefs_spo = modimpl_coefs[modimpl_mask]
|
||||||
|
# sem_modimpl_moys_spo est (nb_etuds, nb_mod_sport)
|
||||||
|
# ou (nb_etuds, nb_mod_sport, nb_ues)
|
||||||
|
nb_etuds, nb_mod_sport = sem_modimpl_moys_spo.shape[:2]
|
||||||
|
nb_ues = len(ues)
|
||||||
|
# Enlève les NaN du numérateur:
|
||||||
|
sem_modimpl_moys_no_nan = np.nan_to_num(sem_modimpl_moys_spo, nan=0.0)
|
||||||
|
|
||||||
|
# Annule les coefs des modules où l'étudiant n'est pas inscrit:
|
||||||
|
if formsemestre.formation.is_apc():
|
||||||
|
# BUT
|
||||||
|
nb_ues_no_bonus = sem_modimpl_moys.shape[2]
|
||||||
|
# Duplique les inscriptions sur les UEs non bonus:
|
||||||
|
modimpl_inscr_spo_stacked = np.stack(
|
||||||
|
[modimpl_inscr_spo] * nb_ues_no_bonus, axis=2
|
||||||
|
)
|
||||||
|
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||||
|
# Annule les notes:
|
||||||
|
sem_modimpl_moys_inscrits = np.where(
|
||||||
|
modimpl_inscr_spo_stacked, sem_modimpl_moys_no_nan, 0.0
|
||||||
|
)
|
||||||
|
# Annule les coefs des modules où l'étudiant n'est pas inscrit:
|
||||||
|
modimpl_coefs_etuds = np.where(
|
||||||
|
modimpl_inscr_spo_stacked,
|
||||||
|
np.stack([modimpl_coefs_spo.T] * nb_etuds),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Formations classiques
|
||||||
|
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||||
|
# Annule les notes:
|
||||||
|
sem_modimpl_moys_inscrits = np.where(
|
||||||
|
modimpl_inscr_spo, sem_modimpl_moys_no_nan, 0.0
|
||||||
|
)
|
||||||
|
modimpl_coefs_spo = modimpl_coefs_spo.T
|
||||||
|
modimpl_coefs_etuds = np.where(
|
||||||
|
modimpl_inscr_spo, np.stack([modimpl_coefs_spo] * nb_etuds), 0.0
|
||||||
|
)
|
||||||
|
# Annule les coefs des modules NaN (nb_etuds x nb_mod_sport)
|
||||||
|
modimpl_coefs_etuds_no_nan = np.where(
|
||||||
|
np.isnan(sem_modimpl_moys_spo), 0.0, modimpl_coefs_etuds
|
||||||
|
)
|
||||||
|
#
|
||||||
|
self.compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
|
||||||
|
|
||||||
|
def compute_bonus(
|
||||||
|
self,
|
||||||
|
sem_modimpl_moys_inscrits: np.ndarray,
|
||||||
|
modimpl_coefs_etuds_no_nan: np.ndarray,
|
||||||
|
):
|
||||||
|
"""Calcul des bonus: méthode virtuelle à écraser.
|
||||||
|
Arguments:
|
||||||
|
- sem_modimpl_moys_inscrits:
|
||||||
|
ndarray (nb_etuds, mod_sport) ou en APC (nb_etuds, mods_sport, nb_ue)
|
||||||
|
les notes aux modules sports auxquel l'étudiant est inscrit, 0 sinon. Pas de nans.
|
||||||
|
- modimpl_coefs_etuds_no_nan:
|
||||||
|
les coefficients: float ndarray
|
||||||
|
|
||||||
|
Résultat: None
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("méthode virtuelle")
|
||||||
|
|
||||||
|
def get_bonus_ues(self) -> pd.Series:
|
||||||
|
"""Les bonus à appliquer aux UE
|
||||||
|
Résultat: DataFrame de float, index etudid, columns: ue.id
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
self.formsemestre.formation.is_apc()
|
||||||
|
and self.apc_apply_bonus_mg_to_ues
|
||||||
|
and self.bonus_ues is None
|
||||||
|
):
|
||||||
|
# reporte uniformément le bonus moyenne générale sur les UEs
|
||||||
|
# (assure la compatibilité de la plupart des anciens bonus avec le BUT)
|
||||||
|
# ues = self.formsemestre.query_ues(with_sport=False)
|
||||||
|
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
||||||
|
bonus_moy_gen = self.get_bonus_moy_gen()
|
||||||
|
bonus_ues = np.stack([bonus_moy_gen.values] * len(ues_idx), axis=1)
|
||||||
|
return pd.DataFrame(bonus_ues, index=self.etuds_idx, columns=ues_idx)
|
||||||
|
|
||||||
|
return self.bonus_ues
|
||||||
|
|
||||||
|
def get_bonus_moy_gen(self):
|
||||||
|
"""Le bonus à appliquer à la moyenne générale.
|
||||||
|
Résultat: Series de float, index etudid
|
||||||
|
"""
|
||||||
|
return self.bonus_moy_gen
|
||||||
|
|
||||||
|
|
||||||
|
class BonusSportSimples(BonusSport):
|
||||||
|
"""Les bonus sport simples calcule un bonus à partir des notes moyennes de modules
|
||||||
|
de l'UE sport, et ce bonus est soit appliqué sur la moyenne générale (formations classiques),
|
||||||
|
soit réparti sur les UE (formations APC).
|
||||||
|
|
||||||
|
Le bonus est par défaut calculé comme:
|
||||||
|
Les points au-dessus du seuil (par défaut) 10 sur 20 obtenus dans chacun des
|
||||||
|
modules optionnels sont cumulés et une fraction de ces points cumulés s'ajoute
|
||||||
|
à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
seuil_moy_gen = 10.0 # seuls les points au dessus du seuil sont comptés
|
||||||
|
proportion_point = 0.05 # multiplie les points au dessus du seuil
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus"""
|
||||||
|
bonus_moy_gen_arr = np.sum(
|
||||||
|
np.where(
|
||||||
|
sem_modimpl_moys_inscrits > self.seuil_moy_gen,
|
||||||
|
(sem_modimpl_moys_inscrits - self.seuil_moy_gen)
|
||||||
|
* self.proportion_point,
|
||||||
|
0.0,
|
||||||
|
),
|
||||||
|
axis=1,
|
||||||
|
)
|
||||||
|
# en APC, applati la moyenne gen. XXX pourrait être fait en amont
|
||||||
|
if len(bonus_moy_gen_arr.shape) > 1:
|
||||||
|
bonus_moy_gen_arr = bonus_moy_gen_arr.sum(axis=1)
|
||||||
|
# Bonus moyenne générale, et 0 sur les UE
|
||||||
|
self.bonus_moy_gen = pd.Series(
|
||||||
|
bonus_moy_gen_arr, index=self.etuds_idx, dtype=float
|
||||||
|
)
|
||||||
|
if self.bonus_moy_gen_limit is not None:
|
||||||
|
# Seuil: bonus (sur moy. gen.) limité à bonus_moy_gen_limit points
|
||||||
|
self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_moy_gen_limit)
|
||||||
|
|
||||||
|
# Laisse bonus_ues à None, en APC le bonus moy. gen. sera réparti sur les UEs.
|
||||||
|
|
||||||
|
|
||||||
|
# bonus_ue = np.stack([modimpl_coefs_spo.T] * nb_ues)
|
||||||
|
|
||||||
|
|
||||||
|
class BonusIUTV(BonusSportSimples):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Villetaneuse
|
||||||
|
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
de l'Université Paris 13 (sports, musique, deuxième langue,
|
||||||
|
culture, etc) non rattachés à une unité d'enseignement. Les points
|
||||||
|
au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||||
|
optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
|
||||||
|
la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutv"
|
||||||
|
pass # oui, c'ets le bonus par défaut
|
||||||
|
|
||||||
|
|
||||||
|
class BonusDirect(BonusSportSimples):
|
||||||
|
"""Bonus direct: les points sont directement ajoutés à la moyenne générale.
|
||||||
|
Les coefficients sont ignorés: tous les points de bonus sont sommés.
|
||||||
|
(rappel: la note est ramenée sur 20 avant application).
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_direct"
|
||||||
|
seuil_moy_gen = 0.0 # seuls le spoints au dessus du seuil sont comptés
|
||||||
|
proportion_point = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
class BonusIUTStDenis(BonusIUTV):
|
||||||
|
"""Semblable à bonus_iutv mais sans coefficients et total limité à 0.5 points."""
|
||||||
|
|
||||||
|
name = "bonus_iut_stdenis"
|
||||||
|
bonus_moy_gen_limit = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
class BonusColmar(BonusSportSimples):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Colmar.
|
||||||
|
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
de l'U.H.A. (sports, musique, deuxième langue, culture, etc) non
|
||||||
|
rattachés à une unité d'enseignement. Les points au-dessus de 10
|
||||||
|
sur 20 obtenus dans chacune des matières optionnelles sont cumulés
|
||||||
|
dans la limite de 10 points. 5% de ces points cumulés s'ajoutent à
|
||||||
|
la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# note: cela revient à dire que l'on ajoute 5% des points au dessus de 10,
|
||||||
|
# et qu'on limite à 5% de 10, soit 0.5 points
|
||||||
|
# ce bonus est donc strictement identique à celui de St Denis (BonusIUTStDenis)
|
||||||
|
name = "bonus_colmar"
|
||||||
|
bonus_moy_gen_limit = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
class BonusVilleAvray:
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Ville d'Avray
|
||||||
|
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
de l'Université Paris 10 (C2I) non rattachés à une unité d'enseignement.
|
||||||
|
Si la note est >= 10 et < 12, bonus de 0.1 point
|
||||||
|
Si la note est >= 12 et < 16, bonus de 0.2 point
|
||||||
|
Si la note est >= 16, bonus de 0.3 point
|
||||||
|
Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
|
||||||
|
l'étudiant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutva"
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus"""
|
||||||
|
# Calcule moyenne pondérée des notes de sport:
|
||||||
|
bonus_moy_gen_arr = np.sum(
|
||||||
|
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||||
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
|
bonus_moy_gen_arr[bonus_moy_gen_arr >= 10.0] = 0.1
|
||||||
|
bonus_moy_gen_arr[bonus_moy_gen_arr >= 12.0] = 0.2
|
||||||
|
bonus_moy_gen_arr[bonus_moy_gen_arr >= 16.0] = 0.3
|
||||||
|
|
||||||
|
# Bonus moyenne générale, et 0 sur les UE
|
||||||
|
self.bonus_moy_gen = pd.Series(
|
||||||
|
bonus_moy_gen_arr, index=self.etuds_idx, dtype=float
|
||||||
|
)
|
||||||
|
if self.bonus_moy_gen_limit is not None:
|
||||||
|
# Seuil: bonus (sur moy. gen.) limité à bonus_moy_gen_limit points
|
||||||
|
self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_moy_gen_limit)
|
||||||
|
|
||||||
|
# Laisse bonus_ues à None, en APC le bonus moy. gen. sera réparti sur les UEs.
|
||||||
|
|
||||||
|
|
||||||
|
def get_bonus_class_dict(start=BonusSport, d=None):
|
||||||
|
"""Dictionnaire des classes de bonus
|
||||||
|
(liste les sous-classes de BonusSport ayant un nom)
|
||||||
|
Resultat: { name : class }
|
||||||
|
"""
|
||||||
|
if d is None:
|
||||||
|
d = {}
|
||||||
|
if start.name != "virtual":
|
||||||
|
d[start.name] = start
|
||||||
|
for subclass in start.__subclasses__():
|
||||||
|
get_bonus_class_dict(subclass, d=d)
|
||||||
|
return d
|
@ -21,7 +21,7 @@ def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
|
|||||||
value: bool (0/1 inscrit ou pas)
|
value: bool (0/1 inscrit ou pas)
|
||||||
"""
|
"""
|
||||||
# méthode la moins lente: une requete par module, merge les dataframes
|
# méthode la moins lente: une requete par module, merge les dataframes
|
||||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
moduleimpl_ids = [m.id for m in formsemestre.modimpls_sorted]
|
||||||
etudids = [inscr.etudid for inscr in formsemestre.inscriptions]
|
etudids = [inscr.etudid for inscr in formsemestre.inscriptions]
|
||||||
df = pd.DataFrame(index=etudids, dtype=int)
|
df = pd.DataFrame(index=etudids, dtype=int)
|
||||||
for moduleimpl_id in moduleimpl_ids:
|
for moduleimpl_id in moduleimpl_ids:
|
||||||
@ -47,10 +47,10 @@ def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
|
|||||||
|
|
||||||
def df_load_modimpl_inscr_v0(formsemestre):
|
def df_load_modimpl_inscr_v0(formsemestre):
|
||||||
# methode 0, pur SQL Alchemy, 1.5 à 2 fois plus lente
|
# methode 0, pur SQL Alchemy, 1.5 à 2 fois plus lente
|
||||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
moduleimpl_ids = [m.id for m in formsemestre.modimpls_sorted]
|
||||||
etudids = [i.etudid for i in formsemestre.inscriptions]
|
etudids = [i.etudid for i in formsemestre.inscriptions]
|
||||||
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
|
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls_sorted:
|
||||||
ins_mod = df[modimpl.id]
|
ins_mod = df[modimpl.id]
|
||||||
for inscr in modimpl.inscriptions:
|
for inscr in modimpl.inscriptions:
|
||||||
ins_mod[inscr.etudid] = True
|
ins_mod[inscr.etudid] = True
|
||||||
@ -58,7 +58,7 @@ def df_load_modimpl_inscr_v0(formsemestre):
|
|||||||
|
|
||||||
|
|
||||||
def df_load_modimpl_inscr_v2(formsemestre):
|
def df_load_modimpl_inscr_v2(formsemestre):
|
||||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
moduleimpl_ids = [m.id for m in formsemestre.modimpls_sorted]
|
||||||
etudids = [i.etudid for i in formsemestre.inscriptions]
|
etudids = [i.etudid for i in formsemestre.inscriptions]
|
||||||
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
|
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
|
||||||
cursor = db.engine.execute(
|
cursor = db.engine.execute(
|
||||||
|
@ -65,8 +65,9 @@ class ModuleImplResults:
|
|||||||
self.module_id = moduleimpl.module.id
|
self.module_id = moduleimpl.module.id
|
||||||
self.etudids = None
|
self.etudids = None
|
||||||
"liste des étudiants inscrits au SEMESTRE"
|
"liste des étudiants inscrits au SEMESTRE"
|
||||||
|
|
||||||
self.nb_inscrits_module = None
|
self.nb_inscrits_module = None
|
||||||
"nombre d'inscrits (non DEM) au module"
|
"nombre d'inscrits (non DEM) à ce module"
|
||||||
self.evaluations_completes = []
|
self.evaluations_completes = []
|
||||||
"séquence de booléens, indiquant les évals à prendre en compte."
|
"séquence de booléens, indiquant les évals à prendre en compte."
|
||||||
self.evaluations_completes_dict = {}
|
self.evaluations_completes_dict = {}
|
||||||
@ -263,14 +264,12 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
return self.etuds_moy_module
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
|
||||||
def load_evaluations_poids(
|
def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
||||||
moduleimpl_id: int, default_poids=1.0
|
|
||||||
) -> tuple[pd.DataFrame, list]:
|
|
||||||
"""Charge poids des évaluations d'un module et retourne un dataframe
|
"""Charge poids des évaluations d'un module et retourne un dataframe
|
||||||
rows = evaluations, columns = UE, value = poids (float).
|
rows = evaluations, columns = UE, value = poids (float).
|
||||||
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
||||||
remplies par default_poids.
|
remplies: 1 si le coef de ce module dans l'UE est non nul, zéro sinon.
|
||||||
Résultat: (evals_poids, liste de UE du semestre)
|
Résultat: (evals_poids, liste de UEs du semestre sauf le sport)
|
||||||
"""
|
"""
|
||||||
modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
||||||
@ -282,8 +281,14 @@ def load_evaluations_poids(
|
|||||||
EvaluationUEPoids.evaluation
|
EvaluationUEPoids.evaluation
|
||||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||||
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
||||||
if default_poids is not None:
|
# Initialise poids non enregistrés:
|
||||||
evals_poids.fillna(value=default_poids, inplace=True)
|
if np.isnan(evals_poids.values.flat).any():
|
||||||
|
ue_coefs = modimpl.module.get_ue_coef_dict()
|
||||||
|
for ue in ues:
|
||||||
|
evals_poids[ue.id][evals_poids[ue.id].isna()] = (
|
||||||
|
1 if ue_coefs.get(ue.id, 0.0) > 0 else 0
|
||||||
|
)
|
||||||
|
|
||||||
return evals_poids, ues
|
return evals_poids, ues
|
||||||
|
|
||||||
|
|
||||||
@ -296,6 +301,7 @@ def moduleimpl_is_conforme(
|
|||||||
évaluations vers une UE de coefficient non nul est non nulle.
|
évaluations vers une UE de coefficient non nul est non nulle.
|
||||||
|
|
||||||
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||||
|
NB: les UEs dans evals_poids sont sans le bonus sport
|
||||||
"""
|
"""
|
||||||
nb_evals, nb_ues = evals_poids.shape
|
nb_evals, nb_ues = evals_poids.shape
|
||||||
if nb_evals == 0:
|
if nb_evals == 0:
|
||||||
|
@ -38,7 +38,7 @@ def compute_sem_moys_apc(
|
|||||||
= moyenne des moyennes d'UE, pondérée par la somme de leurs coefs
|
= moyenne des moyennes d'UE, pondérée par la somme de leurs coefs
|
||||||
|
|
||||||
etud_moy_ue_df: DataFrame, colonnes ue_id, lignes etudid
|
etud_moy_ue_df: DataFrame, colonnes ue_id, lignes etudid
|
||||||
modimpl_coefs_df: DataFrame, colonnes moduleimpl_id, lignes UE
|
modimpl_coefs_df: DataFrame, colonnes moduleimpl_id, lignes UE (sans ue bonus)
|
||||||
|
|
||||||
Result: panda Series, index etudid, valeur float (moyenne générale)
|
Result: panda Series, index etudid, valeur float (moyenne générale)
|
||||||
"""
|
"""
|
||||||
|
@ -62,6 +62,10 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
|||||||
.filter(
|
.filter(
|
||||||
(Module.module_type == ModuleType.RESSOURCE)
|
(Module.module_type == ModuleType.RESSOURCE)
|
||||||
| (Module.module_type == ModuleType.SAE)
|
| (Module.module_type == ModuleType.SAE)
|
||||||
|
| (
|
||||||
|
(Module.ue_id == UniteEns.id)
|
||||||
|
& (UniteEns.type == sco_codes_parcours.UE_SPORT)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.order_by(
|
.order_by(
|
||||||
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
||||||
@ -102,13 +106,13 @@ def df_load_modimpl_coefs(
|
|||||||
et modules du formsemestre.
|
et modules du formsemestre.
|
||||||
Si ues et modimpls sont None, prend tous ceux du formsemestre.
|
Si ues et modimpls sont None, prend tous ceux du formsemestre.
|
||||||
Résultat: (module_coefs_df, ues, modules)
|
Résultat: (module_coefs_df, ues, modules)
|
||||||
DataFrame rows = UEs, columns = modimpl, value = coef.
|
DataFrame rows = UEs (avec bonus), columns = modimpl, value = coef.
|
||||||
"""
|
"""
|
||||||
if ues is None:
|
if ues is None:
|
||||||
ues = formsemestre.query_ues().all()
|
ues = formsemestre.query_ues().all()
|
||||||
ue_ids = [x.id for x in ues]
|
ue_ids = [x.id for x in ues]
|
||||||
if modimpls is None:
|
if modimpls is None:
|
||||||
modimpls = formsemestre.modimpls.all()
|
modimpls = formsemestre.modimpls_sorted
|
||||||
modimpl_ids = [x.id for x in modimpls]
|
modimpl_ids = [x.id for x in modimpls]
|
||||||
mod2impl = {m.module.id: m.id for m in modimpls}
|
mod2impl = {m.module.id: m.id for m in modimpls}
|
||||||
modimpl_coefs_df = pd.DataFrame(columns=modimpl_ids, index=ue_ids, dtype=float)
|
modimpl_coefs_df = pd.DataFrame(columns=modimpl_ids, index=ue_ids, dtype=float)
|
||||||
@ -134,7 +138,7 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
|||||||
assert len(modimpls_notes)
|
assert len(modimpls_notes)
|
||||||
modimpls_notes_arr = [df.values for df in modimpls_notes]
|
modimpls_notes_arr = [df.values for df in modimpls_notes]
|
||||||
modimpls_notes = np.stack(modimpls_notes_arr)
|
modimpls_notes = np.stack(modimpls_notes_arr)
|
||||||
# passe de (mod x etud x ue) à (etud x mod x UE)
|
# passe de (mod x etud x ue) à (etud x mod x ue)
|
||||||
return modimpls_notes.swapaxes(0, 1)
|
return modimpls_notes.swapaxes(0, 1)
|
||||||
|
|
||||||
|
|
||||||
@ -144,10 +148,14 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||||||
et assemble le cube.
|
et assemble le cube.
|
||||||
|
|
||||||
etuds: tous les inscrits au semestre (avec dem. et def.)
|
etuds: tous les inscrits au semestre (avec dem. et def.)
|
||||||
modimpls: _tous_ les modimpls de ce semestre
|
modimpls: _tous_ les modimpls de ce semestre (y compris bonus sport)
|
||||||
UEs: X?X voir quelles sont les UE considérées ici
|
UEs: toutes les UE du semestre (même si pas d'inscrits) SAUF le sport.
|
||||||
|
|
||||||
Resultat:
|
Attention: la liste des modimpls inclut les modules des UE sport, mais
|
||||||
|
elles ne sont pas dans la troisième dimension car elles n'ont pas de
|
||||||
|
"moyenne d'UE".
|
||||||
|
|
||||||
|
Résultat:
|
||||||
sem_cube : ndarray (etuds x modimpls x UEs)
|
sem_cube : ndarray (etuds x modimpls x UEs)
|
||||||
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
||||||
modimpls_results dict { modimpl.id : ModuleImplResultsAPC }
|
modimpls_results dict { modimpl.id : ModuleImplResultsAPC }
|
||||||
@ -155,7 +163,7 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||||||
modimpls_results = {}
|
modimpls_results = {}
|
||||||
modimpls_evals_poids = {}
|
modimpls_evals_poids = {}
|
||||||
modimpls_notes = []
|
modimpls_notes = []
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls_sorted:
|
||||||
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
||||||
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
||||||
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||||
@ -194,26 +202,27 @@ def compute_ue_moys_apc(
|
|||||||
modimpls : liste des modules à considérer (dim. 1 du cube)
|
modimpls : liste des modules à considérer (dim. 1 du cube)
|
||||||
ues : liste des UE (dim. 2 du cube)
|
ues : liste des UE (dim. 2 du cube)
|
||||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||||
modimpl_coefs_df: matrice coefficients (UE x modimpl)
|
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport
|
||||||
|
|
||||||
Resultat: DataFrame columns UE, rows etudid
|
Résultat: DataFrame columns UE (sans sport), rows etudid
|
||||||
"""
|
"""
|
||||||
nb_etuds, nb_modules, nb_ues = sem_cube.shape
|
nb_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape
|
||||||
|
nb_ues_tot = len(ues)
|
||||||
assert len(modimpls) == nb_modules
|
assert len(modimpls) == nb_modules
|
||||||
if nb_modules == 0 or nb_etuds == 0:
|
if nb_modules == 0 or nb_etuds == 0:
|
||||||
return pd.DataFrame(
|
return pd.DataFrame(
|
||||||
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
||||||
)
|
)
|
||||||
assert len(etuds) == nb_etuds
|
assert len(etuds) == nb_etuds
|
||||||
assert len(ues) == nb_ues
|
|
||||||
assert modimpl_inscr_df.shape[0] == nb_etuds
|
assert modimpl_inscr_df.shape[0] == nb_etuds
|
||||||
assert modimpl_inscr_df.shape[1] == nb_modules
|
assert modimpl_inscr_df.shape[1] == nb_modules
|
||||||
assert modimpl_coefs_df.shape[0] == nb_ues
|
assert modimpl_coefs_df.shape[0] == nb_ues_no_bonus
|
||||||
assert modimpl_coefs_df.shape[1] == nb_modules
|
assert modimpl_coefs_df.shape[1] == nb_modules
|
||||||
modimpl_inscr = modimpl_inscr_df.values
|
modimpl_inscr = modimpl_inscr_df.values
|
||||||
modimpl_coefs = modimpl_coefs_df.values
|
modimpl_coefs = modimpl_coefs_df.values
|
||||||
# Duplique les inscriptions sur les UEs:
|
|
||||||
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues, axis=2)
|
# Duplique les inscriptions sur les UEs non bonus:
|
||||||
|
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues_no_bonus, axis=2)
|
||||||
# Enlève les NaN du numérateur:
|
# Enlève les NaN du numérateur:
|
||||||
# si on veut prendre en compte les modules avec notes neutralisées ?
|
# si on veut prendre en compte les modules avec notes neutralisées ?
|
||||||
sem_cube_no_nan = np.nan_to_num(sem_cube, nan=0.0)
|
sem_cube_no_nan = np.nan_to_num(sem_cube, nan=0.0)
|
||||||
@ -234,7 +243,9 @@ def compute_ue_moys_apc(
|
|||||||
modimpl_coefs_etuds_no_nan * sem_cube_inscrits, axis=1
|
modimpl_coefs_etuds_no_nan * sem_cube_inscrits, axis=1
|
||||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
return pd.DataFrame(
|
return pd.DataFrame(
|
||||||
etud_moy_ue, index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
etud_moy_ue,
|
||||||
|
index=modimpl_inscr_df.index, # les etudids
|
||||||
|
columns=modimpl_coefs_df.index, # les UE sans les UE bonus sport
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -244,6 +255,7 @@ def compute_ue_moys_classic(
|
|||||||
ues: list,
|
ues: list,
|
||||||
modimpl_inscr_df: pd.DataFrame,
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
modimpl_coefs: np.array,
|
modimpl_coefs: np.array,
|
||||||
|
modimpl_mask: np.array,
|
||||||
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
||||||
"""Calcul de la moyenne d'UE en mode classique.
|
"""Calcul de la moyenne d'UE en mode classique.
|
||||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||||
@ -251,13 +263,19 @@ def compute_ue_moys_classic(
|
|||||||
NA pas de notes disponibles
|
NA pas de notes disponibles
|
||||||
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
||||||
|
|
||||||
sem_matrix: notes moyennes aux modules
|
L'éventuel bonus sport n'est PAS appliqué ici.
|
||||||
|
|
||||||
|
Le masque modimpl_mask est un tableau de booléens (un par modimpl) qui
|
||||||
|
permet de sélectionner un sous-ensemble de modules (SAEs, tout sauf sport, ...).
|
||||||
|
|
||||||
|
sem_matrix: notes moyennes aux modules (tous les étuds x tous les modimpls)
|
||||||
ndarray (etuds x modimpls)
|
ndarray (etuds x modimpls)
|
||||||
(floats avec des NaN)
|
(floats avec des NaN)
|
||||||
etuds : listes des étudiants (dim. 0 de la matrice)
|
etuds : listes des étudiants (dim. 0 de la matrice)
|
||||||
ues : liste des UE
|
ues : liste des UE du semestre
|
||||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||||
modimpl_coefs: vecteur des coefficients de modules
|
modimpl_coefs: vecteur des coefficients de modules
|
||||||
|
modimpl_mask: masque des modimpls à prendre en compte
|
||||||
|
|
||||||
Résultat:
|
Résultat:
|
||||||
- moyennes générales: pd.Series, index etudid
|
- moyennes générales: pd.Series, index etudid
|
||||||
@ -266,10 +284,15 @@ def compute_ue_moys_classic(
|
|||||||
les coefficients effectifs de chaque UE pour chaque étudiant
|
les coefficients effectifs de chaque UE pour chaque étudiant
|
||||||
(sommes de coefs de modules pris en compte)
|
(sommes de coefs de modules pris en compte)
|
||||||
"""
|
"""
|
||||||
|
# Restreint aux modules sélectionnés:
|
||||||
|
sem_matrix = sem_matrix[:, modimpl_mask]
|
||||||
|
modimpl_inscr = modimpl_inscr_df.values[:, modimpl_mask]
|
||||||
|
modimpl_coefs = modimpl_coefs[modimpl_mask]
|
||||||
|
|
||||||
nb_etuds, nb_modules = sem_matrix.shape
|
nb_etuds, nb_modules = sem_matrix.shape
|
||||||
assert len(modimpl_coefs) == nb_modules
|
assert len(modimpl_coefs) == nb_modules
|
||||||
nb_ues = len(ues)
|
nb_ues = len(ues)
|
||||||
modimpl_inscr = modimpl_inscr_df.values
|
|
||||||
# Enlève les NaN du numérateur:
|
# Enlève les NaN du numérateur:
|
||||||
sem_matrix_no_nan = np.nan_to_num(sem_matrix, nan=0.0)
|
sem_matrix_no_nan = np.nan_to_num(sem_matrix, nan=0.0)
|
||||||
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||||
@ -291,8 +314,8 @@ def compute_ue_moys_classic(
|
|||||||
etud_moy_gen_s = pd.Series(etud_moy_gen, index=modimpl_inscr_df.index)
|
etud_moy_gen_s = pd.Series(etud_moy_gen, index=modimpl_inscr_df.index)
|
||||||
# Calcul des moyennes d'UE
|
# Calcul des moyennes d'UE
|
||||||
ue_modules = np.array(
|
ue_modules = np.array(
|
||||||
[[m.module.ue == ue for m in formsemestre.modimpls] for ue in ues]
|
[[m.module.ue == ue for m in formsemestre.modimpls_sorted] for ue in ues]
|
||||||
)[..., np.newaxis]
|
)[..., np.newaxis][:, modimpl_mask, :]
|
||||||
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
||||||
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
||||||
)
|
)
|
||||||
|
@ -10,6 +10,9 @@ import pandas as pd
|
|||||||
|
|
||||||
from app.comp import moy_ue, moy_sem, inscr_mod
|
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
|
from app.comp.bonus_spo import BonusSport
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
|
||||||
|
|
||||||
class ResultatsSemestreBUT(NotesTableCompat):
|
class ResultatsSemestreBUT(NotesTableCompat):
|
||||||
@ -37,26 +40,44 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
) = moy_ue.notes_sem_load_cube(self.formsemestre)
|
) = moy_ue.notes_sem_load_cube(self.formsemestre)
|
||||||
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
||||||
self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
|
self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
|
||||||
self.formsemestre, ues=self.ues, modimpls=self.modimpls
|
self.formsemestre, ues=self.ues, modimpls=self.formsemestre.modimpls_sorted
|
||||||
)
|
)
|
||||||
# l'idx de la colonne du mod modimpl.id est
|
# l'idx de la colonne du mod modimpl.id est
|
||||||
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||||
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
||||||
|
|
||||||
|
# Elimine les coefs des UE bonus sports
|
||||||
|
no_bonus = [ue.type != UE_SPORT for ue in self.ues]
|
||||||
|
modimpl_coefs_no_bonus_df = self.modimpl_coefs_df[no_bonus]
|
||||||
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||||
self.sem_cube,
|
self.sem_cube,
|
||||||
self.etuds,
|
self.etuds,
|
||||||
self.modimpls,
|
self.formsemestre.modimpls_sorted,
|
||||||
self.ues,
|
self.ues,
|
||||||
self.modimpl_inscr_df,
|
self.modimpl_inscr_df,
|
||||||
self.modimpl_coefs_df,
|
modimpl_coefs_no_bonus_df,
|
||||||
)
|
)
|
||||||
# Les coefficients d'UE ne sont pas utilisés en APC
|
# Les coefficients d'UE ne sont pas utilisés en APC
|
||||||
self.etud_coef_ue_df = pd.DataFrame(
|
self.etud_coef_ue_df = pd.DataFrame(
|
||||||
1.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
|
1.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
|
||||||
)
|
)
|
||||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||||
self.etud_moy_ue, self.modimpl_coefs_df
|
self.etud_moy_ue, modimpl_coefs_no_bonus_df
|
||||||
)
|
)
|
||||||
|
# --- Bonus Sport & Culture
|
||||||
|
bonus_class = ScoDocSiteConfig.get_bonus_sport_class()
|
||||||
|
if bonus_class is not None:
|
||||||
|
bonus: BonusSport = bonus_class(
|
||||||
|
self.formsemestre,
|
||||||
|
self.sem_cube,
|
||||||
|
self.ues,
|
||||||
|
self.modimpl_inscr_df,
|
||||||
|
self.modimpl_coefs_df.transpose(),
|
||||||
|
)
|
||||||
|
self.bonus_ues = bonus.get_bonus_ues()
|
||||||
|
if self.bonus_ues is not None:
|
||||||
|
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
||||||
|
|
||||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
||||||
|
|
||||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||||
|
@ -11,7 +11,11 @@ import pandas as pd
|
|||||||
|
|
||||||
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
|
from app.comp.bonus_spo import BonusSport
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
class ResultatsSemestreClassic(NotesTableCompat):
|
class ResultatsSemestreClassic(NotesTableCompat):
|
||||||
@ -41,11 +45,20 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
)
|
)
|
||||||
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
||||||
self.modimpl_coefs = np.array(
|
self.modimpl_coefs = np.array(
|
||||||
[m.module.coefficient for m in self.formsemestre.modimpls]
|
[m.module.coefficient for m in self.formsemestre.modimpls_sorted]
|
||||||
)
|
)
|
||||||
self.modimpl_idx = {m.id: i for i, m in enumerate(self.formsemestre.modimpls)}
|
self.modimpl_idx = {
|
||||||
|
m.id: i for i, m in enumerate(self.formsemestre.modimpls_sorted)
|
||||||
|
}
|
||||||
"l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]"
|
"l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]"
|
||||||
|
|
||||||
|
modimpl_standards_mask = np.array(
|
||||||
|
[
|
||||||
|
(m.module.module_type == ModuleType.STANDARD)
|
||||||
|
and (m.module.ue.type != UE_SPORT)
|
||||||
|
for m in self.formsemestre.modimpls_sorted
|
||||||
|
]
|
||||||
|
)
|
||||||
(
|
(
|
||||||
self.etud_moy_gen,
|
self.etud_moy_gen,
|
||||||
self.etud_moy_ue,
|
self.etud_moy_ue,
|
||||||
@ -56,7 +69,28 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
self.ues,
|
self.ues,
|
||||||
self.modimpl_inscr_df,
|
self.modimpl_inscr_df,
|
||||||
self.modimpl_coefs,
|
self.modimpl_coefs,
|
||||||
|
modimpl_standards_mask,
|
||||||
)
|
)
|
||||||
|
# --- Bonus Sport & Culture
|
||||||
|
bonus_class = ScoDocSiteConfig.get_bonus_sport_class()
|
||||||
|
if bonus_class is not None:
|
||||||
|
bonus: BonusSport = bonus_class(
|
||||||
|
self.formsemestre,
|
||||||
|
self.sem_matrix,
|
||||||
|
self.ues,
|
||||||
|
self.modimpl_inscr_df,
|
||||||
|
self.modimpl_coefs,
|
||||||
|
)
|
||||||
|
self.bonus_ues = bonus.get_bonus_ues()
|
||||||
|
if self.bonus_ues is not None:
|
||||||
|
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
||||||
|
bonus_mg = bonus.get_bonus_moy_gen()
|
||||||
|
if bonus_mg is not None:
|
||||||
|
self.etud_moy_gen += bonus_mg
|
||||||
|
self.bonus = (
|
||||||
|
bonus_mg # compat nt, utilisé pour l'afficher sur les bulletins
|
||||||
|
)
|
||||||
|
# --- Classements:
|
||||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
||||||
|
|
||||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||||
@ -85,9 +119,9 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple:
|
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple[np.ndarray, dict]:
|
||||||
"""Calcule la matrice des notes du semestre
|
"""Calcule la matrice des notes du semestre
|
||||||
(charge toutes les notes, calcule les moyenne des modules
|
(charge toutes les notes, calcule les moyennes des modules
|
||||||
et assemble la matrice)
|
et assemble la matrice)
|
||||||
Resultat:
|
Resultat:
|
||||||
sem_matrix : 2d-array (etuds x modimpls)
|
sem_matrix : 2d-array (etuds x modimpls)
|
||||||
@ -95,7 +129,7 @@ def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple:
|
|||||||
"""
|
"""
|
||||||
modimpls_results = {}
|
modimpls_results = {}
|
||||||
modimpls_notes = []
|
modimpls_notes = []
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls_sorted:
|
||||||
mod_results = moy_mod.ModuleImplResultsClassic(modimpl)
|
mod_results = moy_mod.ModuleImplResultsClassic(modimpl)
|
||||||
etuds_moy_module = mod_results.compute_module_moy()
|
etuds_moy_module = mod_results.compute_module_moy()
|
||||||
modimpls_results[modimpl.id] = mod_results
|
modimpls_results[modimpl.id] = mod_results
|
||||||
|
@ -100,42 +100,28 @@ class ResultatsSemestre:
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ues(self) -> list[UniteEns]:
|
def ues(self) -> list[UniteEns]:
|
||||||
"""Liste des UEs du semestre
|
"""Liste des UEs du semestre (avec les UE bonus sport)
|
||||||
(indices des DataFrames)
|
(indices des DataFrames)
|
||||||
"""
|
"""
|
||||||
return self.formsemestre.query_ues(with_sport=True).all()
|
return self.formsemestre.query_ues(with_sport=True).all()
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def modimpls(self):
|
|
||||||
"""Liste des modimpls du semestre
|
|
||||||
- triée par numéro de module en APC
|
|
||||||
- triée par numéros d'UE/matières/modules pour les formations standard.
|
|
||||||
"""
|
|
||||||
modimpls = self.formsemestre.modimpls.all()
|
|
||||||
if self.is_apc:
|
|
||||||
modimpls.sort(key=lambda m: (m.module.numero, m.module.code))
|
|
||||||
else:
|
|
||||||
modimpls.sort(
|
|
||||||
key=lambda m: (
|
|
||||||
m.module.ue.numero,
|
|
||||||
m.module.matiere.numero,
|
|
||||||
m.module.numero,
|
|
||||||
m.module.code,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return modimpls
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ressources(self):
|
def ressources(self):
|
||||||
"Liste des ressources du semestre, triées par numéro de module"
|
"Liste des ressources du semestre, triées par numéro de module"
|
||||||
return [
|
return [
|
||||||
m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
|
m
|
||||||
|
for m in self.formsemestre.modimpls_sorted
|
||||||
|
if m.module.module_type == scu.ModuleType.RESSOURCE
|
||||||
]
|
]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def saes(self):
|
def saes(self):
|
||||||
"Liste des SAÉs du semestre, triées par numéro de module"
|
"Liste des SAÉs du semestre, triées par numéro de module"
|
||||||
return [m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE]
|
return [
|
||||||
|
m
|
||||||
|
for m in self.formsemestre.modimpls_sorted
|
||||||
|
if m.module.module_type == scu.ModuleType.SAE
|
||||||
|
]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ue_validables(self) -> list:
|
def ue_validables(self) -> list:
|
||||||
@ -163,16 +149,20 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
développements (API malcommode et peu efficace).
|
développements (API malcommode et peu efficace).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_cached_attrs = ResultatsSemestre._cached_attrs + ()
|
_cached_attrs = ResultatsSemestre._cached_attrs + (
|
||||||
|
"bonus",
|
||||||
|
"bonus_ues",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, formsemestre: FormSemestre):
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
super().__init__(formsemestre)
|
super().__init__(formsemestre)
|
||||||
|
|
||||||
nb_etuds = len(self.etuds)
|
nb_etuds = len(self.etuds)
|
||||||
self.bonus = defaultdict(lambda: 0.0) # XXX TODO
|
self.bonus = None # virtuel
|
||||||
self.ue_rangs = {u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.ues}
|
self.bonus_ues = None # virtuel
|
||||||
|
self.ue_rangs = {u.id: (None, nb_etuds) for u in self.ues}
|
||||||
self.mod_rangs = {
|
self.mod_rangs = {
|
||||||
m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.modimpls
|
m.id: (None, nb_etuds) for m in self.formsemestre.modimpls_sorted
|
||||||
}
|
}
|
||||||
self.moy_min = "NA"
|
self.moy_min = "NA"
|
||||||
self.moy_max = "NA"
|
self.moy_max = "NA"
|
||||||
@ -221,7 +211,11 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
ues = []
|
ues = []
|
||||||
for ue in self.formsemestre.query_ues(with_sport=not filter_sport):
|
for ue in self.formsemestre.query_ues(with_sport=not filter_sport):
|
||||||
d = ue.to_dict()
|
d = ue.to_dict()
|
||||||
d.update(StatsMoyenne(self.etud_moy_ue[ue.id]).to_dict())
|
if ue.type != UE_SPORT:
|
||||||
|
moys = self.etud_moy_ue[ue.id]
|
||||||
|
else:
|
||||||
|
moys = None
|
||||||
|
d.update(StatsMoyenne(moys).to_dict())
|
||||||
ues.append(d)
|
ues.append(d)
|
||||||
return ues
|
return ues
|
||||||
|
|
||||||
@ -230,9 +224,13 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
triés par numéros (selon le type de formation)
|
triés par numéros (selon le type de formation)
|
||||||
"""
|
"""
|
||||||
if ue_id is None:
|
if ue_id is None:
|
||||||
return [m.to_dict() for m in self.modimpls]
|
return [m.to_dict() for m in self.formsemestre.modimpls_sorted]
|
||||||
else:
|
else:
|
||||||
return [m.to_dict() for m in self.modimpls if m.module.ue.id == ue_id]
|
return [
|
||||||
|
m.to_dict()
|
||||||
|
for m in self.formsemestre.modimpls_sorted
|
||||||
|
if m.module.ue.id == ue_id
|
||||||
|
]
|
||||||
|
|
||||||
def get_etud_decision_sem(self, etudid: int) -> dict:
|
def get_etud_decision_sem(self, etudid: int) -> dict:
|
||||||
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
||||||
@ -359,12 +357,16 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
moy_gen = self.etud_moy_gen.get(etudid, False)
|
moy_gen = self.etud_moy_gen.get(etudid, False)
|
||||||
if moy_gen is False:
|
if moy_gen is False:
|
||||||
# pas de moyenne: démissionnaire ou def
|
# pas de moyenne: démissionnaire ou def
|
||||||
t = ["-"] + ["0.00"] * len(self.ues) + ["NI"] * len(self.modimpls)
|
t = (
|
||||||
|
["-"]
|
||||||
|
+ ["0.00"] * len(self.ues)
|
||||||
|
+ ["NI"] * len(self.formsemestre.modimpls_sorted)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
moy_ues = self.etud_moy_ue.loc[etudid]
|
moy_ues = self.etud_moy_ue.loc[etudid]
|
||||||
t = [moy_gen] + list(moy_ues)
|
t = [moy_gen] + list(moy_ues)
|
||||||
# TODO UE capitalisées: ne pas afficher moyennes modules
|
# TODO UE capitalisées: ne pas afficher moyennes modules
|
||||||
for modimpl in self.modimpls:
|
for modimpl in self.formsemestre.modimpls_sorted:
|
||||||
val = self.get_etud_mod_moy(modimpl.id, etudid)
|
val = self.get_etud_mod_moy(modimpl.id, etudid)
|
||||||
t.append(val)
|
t.append(val)
|
||||||
t.append(etudid)
|
t.append(etudid)
|
||||||
|
@ -310,7 +310,7 @@ class ScoDocConfigurationForm(FlaskForm):
|
|||||||
label="Fonction de calcul des bonus sport&culture",
|
label="Fonction de calcul des bonus sport&culture",
|
||||||
choices=[
|
choices=[
|
||||||
(x, x if x else "Aucune")
|
(x, x if x else "Aucune")
|
||||||
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
|
for x in ScoDocSiteConfig.get_bonus_sport_class_names()
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
depts = FieldList(FormField(DeptForm))
|
depts = FieldList(FormField(DeptForm))
|
||||||
@ -363,7 +363,7 @@ class ScoDocConfigurationForm(FlaskForm):
|
|||||||
def select_action(self):
|
def select_action(self):
|
||||||
if (
|
if (
|
||||||
self.data["bonus_sport_func_name"]
|
self.data["bonus_sport_func_name"]
|
||||||
!= ScoDocSiteConfig.get_bonus_sport_func_name()
|
!= ScoDocSiteConfig.get_bonus_sport_class_name()
|
||||||
):
|
):
|
||||||
return BonusSportUpdate(self.data)
|
return BonusSportUpdate(self.data)
|
||||||
for dept_entry in self.depts:
|
for dept_entry in self.depts:
|
||||||
@ -381,7 +381,7 @@ def configuration():
|
|||||||
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
||||||
form = ScoDocConfigurationForm(
|
form = ScoDocConfigurationForm(
|
||||||
data=_make_data(
|
data=_make_data(
|
||||||
bonus_sport=ScoDocSiteConfig.get_bonus_sport_func_name(),
|
bonus_sport=ScoDocSiteConfig.get_bonus_sport_class_name(),
|
||||||
modele=sco_logos.list_logos(),
|
modele=sco_logos.list_logos(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -112,6 +112,9 @@ class FormSemestre(db.Model):
|
|||||||
if self.modalite is None:
|
if self.modalite is None:
|
||||||
self.modalite = FormationModalite.DEFAULT_MODALITE
|
self.modalite = FormationModalite.DEFAULT_MODALITE
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
d = dict(self.__dict__)
|
d = dict(self.__dict__)
|
||||||
d.pop("_sa_instance_state", None)
|
d.pop("_sa_instance_state", None)
|
||||||
@ -152,6 +155,28 @@ class FormSemestre(db.Model):
|
|||||||
sem_ues = sem_ues.filter(UniteEns.type != sco_codes_parcours.UE_SPORT)
|
sem_ues = sem_ues.filter(UniteEns.type != sco_codes_parcours.UE_SPORT)
|
||||||
return sem_ues.order_by(UniteEns.numero)
|
return sem_ues.order_by(UniteEns.numero)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def modimpls_sorted(self) -> list[ModuleImpl]:
|
||||||
|
"""Liste des modimpls du semestre
|
||||||
|
- triée par type/numéro/code en APC
|
||||||
|
- triée par numéros d'UE/matières/modules pour les formations standard.
|
||||||
|
"""
|
||||||
|
modimpls = self.modimpls.all()
|
||||||
|
if self.formation.is_apc():
|
||||||
|
modimpls.sort(
|
||||||
|
key=lambda m: (m.module.module_type, m.module.numero, m.module.code)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
modimpls.sort(
|
||||||
|
key=lambda m: (
|
||||||
|
m.module.ue.numero,
|
||||||
|
m.module.matiere.numero,
|
||||||
|
m.module.numero,
|
||||||
|
m.module.code,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return modimpls
|
||||||
|
|
||||||
def est_courant(self) -> bool:
|
def est_courant(self) -> bool:
|
||||||
"""Vrai si la date actuelle (now) est dans le semestre
|
"""Vrai si la date actuelle (now) est dans le semestre
|
||||||
(les dates de début et fin sont incluses)
|
(les dates de début et fin sont incluses)
|
||||||
|
@ -5,7 +5,7 @@ import pandas as pd
|
|||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.comp import df_cache
|
from app.comp import df_cache
|
||||||
from app.models import UniteEns, Identite
|
from app.models import Identite, Module
|
||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
@ -127,3 +127,16 @@ class ModuleImplInscription(db.Model):
|
|||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
backref=db.backref("inscriptions", cascade="all, delete-orphan"),
|
backref=db.backref("inscriptions", cascade="all, delete-orphan"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def nb_inscriptions_dans_ue(
|
||||||
|
cls, formsemestre_id: int, etudid: int, ue_id: int
|
||||||
|
) -> int:
|
||||||
|
"""Nombre de moduleimpls de l'UE auxquels l'étudiant est inscrit"""
|
||||||
|
return ModuleImplInscription.query.filter(
|
||||||
|
ModuleImplInscription.etudid == etudid,
|
||||||
|
ModuleImplInscription.moduleimpl_id == ModuleImpl.id,
|
||||||
|
ModuleImpl.formsemestre_id == formsemestre_id,
|
||||||
|
ModuleImpl.module_id == Module.id,
|
||||||
|
Module.ue_id == ue_id,
|
||||||
|
).count()
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
from app import db
|
from app import db
|
||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
@ -131,7 +132,8 @@ class Module(db.Model):
|
|||||||
|
|
||||||
def ue_coefs_list(self, include_zeros=True):
|
def ue_coefs_list(self, include_zeros=True):
|
||||||
"""Liste des coefs vers les UE (pour les modules APC).
|
"""Liste des coefs vers les UE (pour les modules APC).
|
||||||
Si include_zeros, liste aussi les UE sans coef (donc nul) de ce semestre.
|
Si include_zeros, liste aussi les UE sans coef (donc nul) de ce semestre,
|
||||||
|
sauf UE bonus sport.
|
||||||
Result: List of tuples [ (ue, coef) ]
|
Result: List of tuples [ (ue, coef) ]
|
||||||
"""
|
"""
|
||||||
if not self.is_apc():
|
if not self.is_apc():
|
||||||
@ -140,6 +142,7 @@ class Module(db.Model):
|
|||||||
# Toutes les UE du même semestre:
|
# Toutes les UE du même semestre:
|
||||||
ues_semestre = (
|
ues_semestre = (
|
||||||
self.formation.ues.filter_by(semestre_idx=self.ue.semestre_idx)
|
self.formation.ues.filter_by(semestre_idx=self.ue.semestre_idx)
|
||||||
|
.filter(UniteEns.type != UE_SPORT)
|
||||||
.order_by(UniteEns.numero)
|
.order_by(UniteEns.numero)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""Model : preferences
|
"""Model : preferences
|
||||||
"""
|
"""
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.scodoc import bonus_sport
|
from app.comp import bonus_spo
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
@ -61,43 +61,76 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_bonus_sport_func(cls, func_name):
|
def set_bonus_sport_class(cls, class_name):
|
||||||
"""Record bonus_sport config.
|
"""Record bonus_sport config.
|
||||||
If func_name not defined, raise NameError
|
If class_name not defined, raise NameError
|
||||||
"""
|
"""
|
||||||
if func_name not in cls.get_bonus_sport_func_names():
|
if class_name not in cls.get_bonus_sport_class_names():
|
||||||
raise NameError("invalid function name for bonus_sport")
|
raise NameError("invalid class name for bonus_sport")
|
||||||
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
||||||
if c:
|
if c:
|
||||||
log("setting to " + func_name)
|
log("setting to " + class_name)
|
||||||
c.value = func_name
|
c.value = class_name
|
||||||
else:
|
else:
|
||||||
c = ScoDocSiteConfig(cls.BONUS_SPORT, func_name)
|
c = ScoDocSiteConfig(cls.BONUS_SPORT, class_name)
|
||||||
db.session.add(c)
|
db.session.add(c)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_bonus_sport_func_name(cls):
|
def get_bonus_sport_class_name(cls):
|
||||||
"""Get configured bonus function name, or None if None."""
|
"""Get configured bonus function name, or None if None."""
|
||||||
f = cls.get_bonus_sport_func_from_name()
|
klass = cls.get_bonus_sport_class_from_name()
|
||||||
if f is None:
|
if klass is None:
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
return f.__name__
|
return klass.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bonus_sport_class(cls):
|
||||||
|
"""Get configured bonus function, or None if None."""
|
||||||
|
return cls.get_bonus_sport_class_from_name()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bonus_sport_class_from_name(cls, class_name=None):
|
||||||
|
"""returns bonus class with specified name.
|
||||||
|
If name not specified, return the configured function.
|
||||||
|
None if no bonus function configured.
|
||||||
|
Raises ScoValueError if class_name not found in module bonus_sport.
|
||||||
|
"""
|
||||||
|
if class_name is None:
|
||||||
|
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
||||||
|
if c is None:
|
||||||
|
return None
|
||||||
|
class_name = c.value
|
||||||
|
if class_name == "": # pas de bonus défini
|
||||||
|
return None
|
||||||
|
klass = bonus_spo.get_bonus_class_dict().get(class_name)
|
||||||
|
if klass is None:
|
||||||
|
raise ScoValueError(
|
||||||
|
f"""Fonction de calcul bonus sport inexistante: {class_name}.
|
||||||
|
(contacter votre administrateur local)."""
|
||||||
|
)
|
||||||
|
return klass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bonus_sport_class_names(cls):
|
||||||
|
"""List available functions names
|
||||||
|
(starting with empty string to represent "no bonus function").
|
||||||
|
"""
|
||||||
|
return [""] + sorted(bonus_spo.get_bonus_class_dict().keys())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_bonus_sport_func(cls):
|
def get_bonus_sport_func(cls):
|
||||||
"""Get configured bonus function, or None if None."""
|
"""Fonction bonus_sport ScoDoc 7 XXX
|
||||||
return cls.get_bonus_sport_func_from_name()
|
Transitoire pour les tests durant la transition #sco92
|
||||||
|
"""
|
||||||
@classmethod
|
|
||||||
def get_bonus_sport_func_from_name(cls, func_name=None):
|
|
||||||
"""returns bonus func with specified name.
|
"""returns bonus func with specified name.
|
||||||
If name not specified, return the configured function.
|
If name not specified, return the configured function.
|
||||||
None if no bonus function configured.
|
None if no bonus function configured.
|
||||||
Raises ScoValueError if func_name not found in module bonus_sport.
|
Raises ScoValueError if func_name not found in module bonus_sport.
|
||||||
"""
|
"""
|
||||||
if func_name is None:
|
from app.scodoc import bonus_sport
|
||||||
|
|
||||||
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
||||||
if c is None:
|
if c is None:
|
||||||
return None
|
return None
|
||||||
@ -111,16 +144,3 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
f"""Fonction de calcul maison inexistante: {func_name}.
|
f"""Fonction de calcul maison inexistante: {func_name}.
|
||||||
(contacter votre administrateur local)."""
|
(contacter votre administrateur local)."""
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_bonus_sport_func_names(cls):
|
|
||||||
"""List available functions names
|
|
||||||
(starting with empty string to represent "no bonus function").
|
|
||||||
"""
|
|
||||||
return [""] + sorted(
|
|
||||||
[
|
|
||||||
getattr(bonus_sport, name).__name__
|
|
||||||
for name in dir(bonus_sport)
|
|
||||||
if name.startswith("bonus_")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
@ -41,6 +41,8 @@ class UniteEns(db.Model):
|
|||||||
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
|
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
|
||||||
coefficient = db.Column(db.Float)
|
coefficient = db.Column(db.Float)
|
||||||
|
|
||||||
|
color = db.Column(db.Text())
|
||||||
|
|
||||||
# relations
|
# relations
|
||||||
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
|
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
|
||||||
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
||||||
|
@ -73,7 +73,8 @@ def TrivialFormulator(
|
|||||||
input_type : 'text', 'textarea', 'password',
|
input_type : 'text', 'textarea', 'password',
|
||||||
'radio', 'menu', 'checkbox',
|
'radio', 'menu', 'checkbox',
|
||||||
'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation),
|
'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation),
|
||||||
'boolcheckbox', 'text_suggest'
|
'boolcheckbox', 'text_suggest',
|
||||||
|
'color'
|
||||||
(default text)
|
(default text)
|
||||||
size : text field width
|
size : text field width
|
||||||
rows, cols: textarea geometry
|
rows, cols: textarea geometry
|
||||||
@ -594,6 +595,11 @@ class TF(object):
|
|||||||
var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
elif input_type == "color":
|
||||||
|
lem.append(
|
||||||
|
'<input type="color" name="%s" id="%s" %s' % (field, field, attribs)
|
||||||
|
)
|
||||||
|
lem.append(('value="%(' + field + ')s" >') % values)
|
||||||
else:
|
else:
|
||||||
raise ValueError("unkown input_type for form (%s)!" % input_type)
|
raise ValueError("unkown input_type for form (%s)!" % input_type)
|
||||||
explanation = descr.get("explanation", "")
|
explanation = descr.get("explanation", "")
|
||||||
@ -712,7 +718,11 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
|||||||
R.append("%s</td>" % title)
|
R.append("%s</td>" % title)
|
||||||
R.append('<td class="tf-ro-field%s">' % klass)
|
R.append('<td class="tf-ro-field%s">' % klass)
|
||||||
|
|
||||||
if input_type == "text" or input_type == "text_suggest":
|
if (
|
||||||
|
input_type == "text"
|
||||||
|
or input_type == "text_suggest"
|
||||||
|
or input_type == "color"
|
||||||
|
):
|
||||||
R.append(("%(" + field + ")s") % self.values)
|
R.append(("%(" + field + ")s") % self.values)
|
||||||
elif input_type in ("radio", "menu", "checkbox", "boolcheckbox"):
|
elif input_type in ("radio", "menu", "checkbox", "boolcheckbox"):
|
||||||
if input_type == "boolcheckbox":
|
if input_type == "boolcheckbox":
|
||||||
|
@ -77,7 +77,6 @@ def bonus_iutv(notes_sport, coefs, infos=None):
|
|||||||
optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
|
optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
|
||||||
la moyenne générale du semestre déjà obtenue par l'étudiant.
|
la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
"""
|
"""
|
||||||
# breakpoint()
|
|
||||||
bonus = sum([(x - 10) / 20.0 for x in notes_sport if x > 10])
|
bonus = sum([(x - 10) / 20.0 for x in notes_sport if x > 10])
|
||||||
return bonus
|
return bonus
|
||||||
|
|
||||||
@ -91,7 +90,7 @@ def bonus_direct(notes_sport, coefs, infos=None):
|
|||||||
|
|
||||||
|
|
||||||
def bonus_iut_stdenis(notes_sport, coefs, infos=None):
|
def bonus_iut_stdenis(notes_sport, coefs, infos=None):
|
||||||
"""Semblable à bonus_iutv mais sans coefficients et total limité à 0.5 points."""
|
"""Semblable à bonus_iutv mais total limité à 0.5 points."""
|
||||||
points = sum([x - 10 for x in notes_sport if x > 10]) # points au dessus de 10
|
points = sum([x - 10 for x in notes_sport if x > 10]) # points au dessus de 10
|
||||||
bonus = points * 0.05 # ou / 20
|
bonus = points * 0.05 # ou / 20
|
||||||
return min(bonus, 0.5) # bonus limité à 1/2 point
|
return min(bonus, 0.5) # bonus limité à 1/2 point
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"""
|
"""
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
from html.entities import name2codepoint
|
from html.entities import name2codepoint
|
||||||
|
from multiprocessing.sharedctypes import Value
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
@ -36,17 +37,23 @@ from flask import g, url_for
|
|||||||
from . import listhistogram
|
from . import listhistogram
|
||||||
|
|
||||||
|
|
||||||
def horizontal_bargraph(value, mark):
|
def horizontal_bargraph(value, mark) -> str:
|
||||||
"""html drawing an horizontal bar and a mark
|
"""html drawing an horizontal bar and a mark
|
||||||
used to vizualize the relative level of a student
|
used to vizualize the relative level of a student
|
||||||
"""
|
"""
|
||||||
tmpl = """
|
try:
|
||||||
|
vals = {"value": int(value), "mark": int(mark)}
|
||||||
|
except ValueError:
|
||||||
|
return ""
|
||||||
|
return (
|
||||||
|
"""
|
||||||
<span class="graph">
|
<span class="graph">
|
||||||
<span class="bar" style="width: %(value)d%%;"></span>
|
<span class="bar" style="width: %(value)d%%;"></span>
|
||||||
<span class="mark" style="left: %(mark)d%%; "></span>
|
<span class="mark" style="left: %(mark)d%%; "></span>
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
return tmpl % {"value": int(value), "mark": int(mark)}
|
% vals
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def histogram_notes(notes):
|
def histogram_notes(notes):
|
||||||
|
@ -170,7 +170,7 @@ class NotesTable:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, formsemestre_id):
|
def __init__(self, formsemestre_id):
|
||||||
log(f"NotesTable( formsemestre_id={formsemestre_id} )")
|
# log(f"NotesTable( formsemestre_id={formsemestre_id} )")
|
||||||
# raise NotImplementedError() # XXX
|
# raise NotImplementedError() # XXX
|
||||||
if not formsemestre_id:
|
if not formsemestre_id:
|
||||||
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
|
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
|
||||||
@ -909,6 +909,7 @@ class NotesTable:
|
|||||||
if len(coefs_bonus_gen) == 1:
|
if len(coefs_bonus_gen) == 1:
|
||||||
coefs_bonus_gen = [1.0] # irrelevant, may be zero
|
coefs_bonus_gen = [1.0] # irrelevant, may be zero
|
||||||
|
|
||||||
|
# XXX attention: utilise anciens bonus_sport, évidemment
|
||||||
bonus_func = ScoDocSiteConfig.get_bonus_sport_func()
|
bonus_func = ScoDocSiteConfig.get_bonus_sport_func()
|
||||||
if bonus_func:
|
if bonus_func:
|
||||||
bonus = bonus_func(
|
bonus = bonus_func(
|
||||||
|
@ -43,6 +43,7 @@ from flask import g, request
|
|||||||
from flask import url_for
|
from flask import url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_mail import Message
|
from flask_mail import Message
|
||||||
|
from app.models.moduleimpls import ModuleImplInscription
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
@ -285,19 +286,29 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
else:
|
else:
|
||||||
I["rang_nt"], I["rang_txt"] = "", ""
|
I["rang_nt"], I["rang_txt"] = "", ""
|
||||||
I["note_max"] = 20.0 # notes toujours sur 20
|
I["note_max"] = 20.0 # notes toujours sur 20
|
||||||
I["bonus_sport_culture"] = nt.bonus[etudid]
|
I["bonus_sport_culture"] = nt.bonus[etudid] if nt.bonus is not None else 0.0
|
||||||
# Liste les UE / modules /evals
|
# Liste les UE / modules /evals
|
||||||
I["ues"] = []
|
I["ues"] = []
|
||||||
I["matieres_modules"] = {}
|
I["matieres_modules"] = {}
|
||||||
I["matieres_modules_capitalized"] = {}
|
I["matieres_modules_capitalized"] = {}
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
|
if (
|
||||||
|
ModuleImplInscription.nb_inscriptions_dans_ue(
|
||||||
|
formsemestre_id, etudid, ue["ue_id"]
|
||||||
|
)
|
||||||
|
== 0
|
||||||
|
):
|
||||||
|
continue
|
||||||
u = ue.copy()
|
u = ue.copy()
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...}
|
u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...}
|
||||||
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
||||||
u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"])
|
u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"])
|
||||||
else:
|
else:
|
||||||
|
if nt.bonus is not None:
|
||||||
x = scu.fmt_note(nt.bonus[etudid], keep_numeric=True)
|
x = scu.fmt_note(nt.bonus[etudid], keep_numeric=True)
|
||||||
|
else:
|
||||||
|
x = ""
|
||||||
if isinstance(x, str):
|
if isinstance(x, str):
|
||||||
u["cur_moy_ue_txt"] = "pas de bonus"
|
u["cur_moy_ue_txt"] = "pas de bonus"
|
||||||
else:
|
else:
|
||||||
|
@ -192,7 +192,9 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
d["note_max"] = dict(value=20) # notes toujours sur 20
|
d["note_max"] = dict(value=20) # notes toujours sur 20
|
||||||
d["bonus_sport_culture"] = dict(value=nt.bonus[etudid])
|
d["bonus_sport_culture"] = dict(
|
||||||
|
value=nt.bonus[etudid] if nt.bonus is not None else 0.0
|
||||||
|
)
|
||||||
|
|
||||||
# Liste les UE / modules /evals
|
# Liste les UE / modules /evals
|
||||||
d["ue"] = []
|
d["ue"] = []
|
||||||
|
@ -195,7 +195,12 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
||||||
doc.append(Element("bonus_sport_culture", value=str(nt.bonus[etudid])))
|
doc.append(
|
||||||
|
Element(
|
||||||
|
"bonus_sport_culture",
|
||||||
|
value=str(nt.bonus[etudid] if nt.bonus is not None else 0.0),
|
||||||
|
)
|
||||||
|
)
|
||||||
# Liste les UE / modules /evals
|
# Liste les UE / modules /evals
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
@ -211,7 +216,7 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
||||||
v = ue_status["cur_moy_ue"]
|
v = ue_status["cur_moy_ue"]
|
||||||
else:
|
else:
|
||||||
v = nt.bonus[etudid]
|
v = nt.bonus[etudid] if nt.bonus is not None else 0.0
|
||||||
x_ue.append(
|
x_ue.append(
|
||||||
Element(
|
Element(
|
||||||
"note",
|
"note",
|
||||||
|
@ -98,8 +98,9 @@ class ScoDocCache:
|
|||||||
status = CACHE.set(key, value, timeout=cls.timeout)
|
status = CACHE.set(key, value, timeout=cls.timeout)
|
||||||
if not status:
|
if not status:
|
||||||
log("Error: cache set failed !")
|
log("Error: cache set failed !")
|
||||||
except:
|
except Exception as exc:
|
||||||
log("XXX CACHE Warning: error in set !!!")
|
log("XXX CACHE Warning: error in set !!!")
|
||||||
|
log(exc)
|
||||||
status = None
|
status = None
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ class BonusSportUpdate(Action):
|
|||||||
def build_action(parameters):
|
def build_action(parameters):
|
||||||
if (
|
if (
|
||||||
parameters["bonus_sport_func_name"]
|
parameters["bonus_sport_func_name"]
|
||||||
!= ScoDocSiteConfig.get_bonus_sport_func_name()
|
!= ScoDocSiteConfig.get_bonus_sport_class_name()
|
||||||
):
|
):
|
||||||
return [BonusSportUpdate(parameters)]
|
return [BonusSportUpdate(parameters)]
|
||||||
return []
|
return []
|
||||||
|
@ -81,6 +81,7 @@ _ueEditor = ndb.EditableTable(
|
|||||||
"is_external",
|
"is_external",
|
||||||
"code_apogee",
|
"code_apogee",
|
||||||
"coefficient",
|
"coefficient",
|
||||||
|
"color",
|
||||||
),
|
),
|
||||||
sortkey="numero",
|
sortkey="numero",
|
||||||
input_formators={
|
input_formators={
|
||||||
@ -358,6 +359,14 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
|
|||||||
"explanation": "réservé pour les capitalisations d'UE effectuées à l'extérieur de l'établissement",
|
"explanation": "réservé pour les capitalisations d'UE effectuées à l'extérieur de l'établissement",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"color",
|
||||||
|
{
|
||||||
|
"input_type": "color",
|
||||||
|
"title": "Couleur",
|
||||||
|
"explanation": "pour affichages",
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
if create and not parcours.UE_IS_MODULE and not is_apc:
|
if create and not parcours.UE_IS_MODULE and not is_apc:
|
||||||
fw.append(
|
fw.append(
|
||||||
|
@ -1107,6 +1107,7 @@ _TABLEAU_MODULES_HEAD = """
|
|||||||
<th class="formsemestre_status">Module</th>
|
<th class="formsemestre_status">Module</th>
|
||||||
<th class="formsemestre_status">Inscrits</th>
|
<th class="formsemestre_status">Inscrits</th>
|
||||||
<th class="resp">Responsable</th>
|
<th class="resp">Responsable</th>
|
||||||
|
<th class="coef">Coefs.</th>
|
||||||
<th class="evals">Évaluations</th>
|
<th class="evals">Évaluations</th>
|
||||||
</tr>
|
</tr>
|
||||||
"""
|
"""
|
||||||
@ -1213,7 +1214,21 @@ def formsemestre_tableau_modules(
|
|||||||
sco_users.user_info(modimpl["responsable_id"])["prenomnom"],
|
sco_users.user_info(modimpl["responsable_id"])["prenomnom"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
H.append("<td>")
|
||||||
|
if mod.module_type in (ModuleType.RESSOURCE, ModuleType.SAE):
|
||||||
|
coefs = mod.ue_coefs_list()
|
||||||
|
for coef in coefs:
|
||||||
|
if coef[1] > 0:
|
||||||
|
H.append(
|
||||||
|
f"""<span class="mod_coef_indicator"
|
||||||
|
title="{coef[0].acronyme}"
|
||||||
|
style="background: {
|
||||||
|
coef[0].color if coef[0].color is not None else 'blue'
|
||||||
|
}"></span>"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append(f"""<span class="mod_coef_indicator_zero"></span>""")
|
||||||
|
H.append("</td>")
|
||||||
if mod.module_type in (
|
if mod.module_type in (
|
||||||
None, # ne devrait pas être nécessaire car la migration a remplacé les NULLs
|
None, # ne devrait pas être nécessaire car la migration a remplacé les NULLs
|
||||||
ModuleType.STANDARD,
|
ModuleType.STANDARD,
|
||||||
|
@ -37,7 +37,10 @@ from app.models.moduleimpls import ModuleImpl
|
|||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.comp import res_sem
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
@ -432,7 +435,7 @@ def _make_table_notes(
|
|||||||
if is_apc:
|
if is_apc:
|
||||||
# Ajoute une colonne par UE
|
# Ajoute une colonne par UE
|
||||||
_add_apc_columns(
|
_add_apc_columns(
|
||||||
moduleimpl_id,
|
modimpl,
|
||||||
evals_poids,
|
evals_poids,
|
||||||
ues,
|
ues,
|
||||||
rows,
|
rows,
|
||||||
@ -815,7 +818,7 @@ def _add_moymod_column(
|
|||||||
|
|
||||||
|
|
||||||
def _add_apc_columns(
|
def _add_apc_columns(
|
||||||
moduleimpl_id,
|
modimpl,
|
||||||
evals_poids,
|
evals_poids,
|
||||||
ues,
|
ues,
|
||||||
rows,
|
rows,
|
||||||
@ -834,18 +837,23 @@ def _add_apc_columns(
|
|||||||
# => On recharge tout dans les nouveaux modèles
|
# => On recharge tout dans les nouveaux modèles
|
||||||
# rows est une liste de dict avec une clé "etudid"
|
# rows est une liste de dict avec une clé "etudid"
|
||||||
# on va y ajouter une clé par UE du semestre
|
# on va y ajouter une clé par UE du semestre
|
||||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
nt: NotesTableCompat = res_sem.load_formsemestre_result(modimpl.formsemestre)
|
||||||
evals_notes, evaluations, evaluations_completes = moy_mod.df_load_modimpl_notes(
|
modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl.id]
|
||||||
moduleimpl_id
|
|
||||||
)
|
# XXX A ENLEVER TODO
|
||||||
etuds_moy_module = moy_mod.compute_module_moy(
|
# modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
evals_notes, evals_poids, evaluations, evaluations_completes
|
|
||||||
)
|
# evals_notes, evaluations, evaluations_completes = moy_mod.df_load_modimpl_notes(
|
||||||
|
# moduleimpl_id
|
||||||
|
# )
|
||||||
|
# etuds_moy_module = moy_mod.compute_module_moy(
|
||||||
|
# evals_notes, evals_poids, evaluations, evaluations_completes
|
||||||
|
# )
|
||||||
if is_conforme:
|
if is_conforme:
|
||||||
# valeur des moyennes vers les UEs:
|
# valeur des moyennes vers les UEs:
|
||||||
for row in rows:
|
for row in rows:
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
moy_ue = etuds_moy_module[ue.id].get(row["etudid"], "?")
|
moy_ue = modimpl_results.etuds_moy_module[ue.id].get(row["etudid"], "?")
|
||||||
row[f"moy_ue_{ue.id}"] = scu.fmt_note(moy_ue, keep_numeric=keep_numeric)
|
row[f"moy_ue_{ue.id}"] = scu.fmt_note(moy_ue, keep_numeric=keep_numeric)
|
||||||
row[f"_moy_ue_{ue.id}_class"] = "moy_ue"
|
row[f"_moy_ue_{ue.id}_class"] = "moy_ue"
|
||||||
# Nom et coefs des UE (lignes titres):
|
# Nom et coefs des UE (lignes titres):
|
||||||
|
@ -171,7 +171,9 @@ def _ue_coefs_html(coefs_lst) -> str:
|
|||||||
"""
|
"""
|
||||||
+ "\n".join(
|
+ "\n".join(
|
||||||
[
|
[
|
||||||
f"""<div style="--coef:{coef}"><div>{coef}</div>{ue.acronyme}</div>"""
|
f"""<div style="--coef:{coef};
|
||||||
|
{'background-color: ' + ue.color + ';' if ue.color else ''}
|
||||||
|
"><div>{coef}</div>{ue.acronyme}</div>"""
|
||||||
for ue, coef in coefs_lst
|
for ue, coef in coefs_lst
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -1308,6 +1308,20 @@ td.formsemestre_status_cell {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.mod_coef_indicator, span.ue_color_indicator {
|
||||||
|
display:inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
span.mod_coef_indicator_zero {
|
||||||
|
display:inline-block;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
border: 1px solid rgb(156, 156, 156);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
span.status_ue_acro { font-weight: bold; }
|
span.status_ue_acro { font-weight: bold; }
|
||||||
span.status_ue_title { font-style: italic; padding-left: 1cm;}
|
span.status_ue_title { font-style: italic; padding-left: 1cm;}
|
||||||
span.status_module_cat { font-weight: bold; }
|
span.status_module_cat { font-weight: bold; }
|
||||||
|
@ -30,6 +30,8 @@
|
|||||||
}}">{% if editable and not ue.modules.count() %}{{icons.delete|safe}}{% else %}{{icons.delete_disabled|safe}}{% endif %}</a>
|
}}">{% if editable and not ue.modules.count() %}{{icons.delete|safe}}{% else %}{{icons.delete_disabled|safe}}{% endif %}</a>
|
||||||
|
|
||||||
<span class="ue_type_{{ue.type}}">
|
<span class="ue_type_{{ue.type}}">
|
||||||
|
<span class="ue_color_indicator" style="background:{{
|
||||||
|
ue.color if ue.color is not none else 'blue'}}"></span>
|
||||||
<b>{{ue.acronyme}}</b> <a class="discretelink" href="{{
|
<b>{{ue.acronyme}}</b> <a class="discretelink" href="{{
|
||||||
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
|
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
|
||||||
>{{ue.titre}}</a>
|
>{{ue.titre}}</a>
|
||||||
|
28
migrations/versions/c95d5a3bf0de_couleur_ue.py
Normal file
28
migrations/versions/c95d5a3bf0de_couleur_ue.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""couleur UE
|
||||||
|
|
||||||
|
Revision ID: c95d5a3bf0de
|
||||||
|
Revises: f40fbaf5831c
|
||||||
|
Create Date: 2022-01-24 21:44:55.205544
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "c95d5a3bf0de"
|
||||||
|
down_revision = "f40fbaf5831c"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column("notes_ue", sa.Column("color", sa.Text(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("notes_ue", "color")
|
||||||
|
# ### end Alembic commands ###
|
@ -133,7 +133,7 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create
|
|||||||
"Create a new user"
|
"Create a new user"
|
||||||
r = Role.get_named_role(role)
|
r = Role.get_named_role(role)
|
||||||
if not r:
|
if not r:
|
||||||
sys.stderr.write("user_create: role {r} does not exists\n".format(r=role))
|
sys.stderr.write("user_create: role {r} does not exist\n".format(r=role))
|
||||||
return 1
|
return 1
|
||||||
u = User.query.filter_by(user_name=username).first()
|
u = User.query.filter_by(user_name=username).first()
|
||||||
if u:
|
if u:
|
||||||
|
Loading…
Reference in New Issue
Block a user