forked from ScoDoc/ScoDoc
WIP: prise en compte des UE capitalisées (! calculs erronés/en cours)
This commit is contained in:
parent
844a90ed62
commit
a9df233c2e
@ -34,6 +34,13 @@ class ValidationsSemestre(ResultatsCache):
|
||||
"""Décisions sur des UEs dans ce semestre:
|
||||
{ etudid : { ue_id : { 'code' : Note|ADM|CMP, 'event_date' }}}
|
||||
"""
|
||||
self.ue_capitalisees: pd.DataFrame = None
|
||||
"""DataFrame, index etudid
|
||||
formsemestre_id : origine de l'UE capitalisée
|
||||
is_external : vrai si validation effectuée dans un semestre extérieur
|
||||
ue_id : dans le semestre origine (pas toujours de la même formation)
|
||||
ue_code : code de l'UE, moy_ue : note enregistrée,
|
||||
event_date : date de la validation (jury)."""
|
||||
|
||||
if not self.load_cached():
|
||||
self.compute()
|
||||
|
@ -12,6 +12,7 @@ from app.comp import moy_ue, moy_sem, inscr_mod
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.comp.bonus_spo import BonusSport
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
|
||||
|
||||
@ -93,11 +94,14 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||
|
||||
# Moyenne générale indicative:
|
||||
# (note: le bonus sport a déjà été appliqué aux moyenens d'UE, et impacte
|
||||
# (note: le bonus sport a déjà été appliqué aux moyennes d'UE, et impacte
|
||||
# donc la moyenne indicative)
|
||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||
self.etud_moy_ue, self.modimpl_coefs_df
|
||||
)
|
||||
# --- UE capitalisées
|
||||
self.apply_capitalisation()
|
||||
|
||||
# --- Classements:
|
||||
self.compute_rangs()
|
||||
|
||||
@ -110,3 +114,12 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
etud_idx = self.etud_index[etudid]
|
||||
# moyenne sur les UE:
|
||||
return self.sem_cube[etud_idx, mod_idx].mean()
|
||||
|
||||
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
|
||||
"""Détermine le coefficient de l'UE pour cet étudiant.
|
||||
N'est utilisé que pour l'injection des UE capitalisées dans la
|
||||
moyenne générale.
|
||||
En BUT, c'est simple: Coef = somme des coefs des modules vers cette UE.
|
||||
(ne dépend pas des modules auxquels est inscrit l'étudiant, ).
|
||||
"""
|
||||
return self.modimpl_coefs_df.loc[ue.id].sum()
|
||||
|
@ -6,15 +6,24 @@
|
||||
|
||||
"""Résultats semestres classiques (non APC)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
from flask import g, url_for
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
from app.comp import moy_mod, moy_ue, inscr_mod
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.comp.bonus_spo import BonusSport
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
@ -104,6 +113,9 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
||||
self.bonus = (
|
||||
bonus_mg # compat nt, utilisé pour l'afficher sur les bulletins
|
||||
)
|
||||
# --- UE capitalisées
|
||||
self.apply_capitalisation()
|
||||
|
||||
# --- Classements:
|
||||
self.compute_rangs()
|
||||
|
||||
@ -132,6 +144,42 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
||||
),
|
||||
}
|
||||
|
||||
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
|
||||
"""Détermine le coefficient de l'UE pour cet étudiant.
|
||||
N'est utilisé que pour l'injection des UE capitalisées dans la
|
||||
moyenne générale.
|
||||
Coef = somme des coefs des modules de l'UE auxquels il est inscrit
|
||||
"""
|
||||
c = comp_etud_sum_coef_modules_ue(self.formsemestre.id, etudid, ue["ue_id"])
|
||||
if c is not None: # inscrit à au moins un module de cette UE
|
||||
return c
|
||||
# arfff: aucun moyen de déterminer le coefficient de façon sûre
|
||||
log(
|
||||
"* oups: calcul coef UE impossible\nformsemestre_id='%s'\netudid='%s'\nue=%s"
|
||||
% (self.formsemestre.id, etudid, ue)
|
||||
)
|
||||
etud: Identite = Identite.query.get(etudid)
|
||||
raise ScoValueError(
|
||||
"""<div class="scovalueerror"><p>Coefficient de l'UE capitalisée %s impossible à déterminer
|
||||
pour l'étudiant <a href="%s" class="discretelink">%s</a></p>
|
||||
<p>Il faut <a href="%s">saisir le coefficient de cette UE avant de continuer</a></p>
|
||||
</div>
|
||||
"""
|
||||
% (
|
||||
ue.acronyme,
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||
etud.nom_disp(),
|
||||
url_for(
|
||||
"notes.formsemestre_edit_uecoefs",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=self.formsemestre.id,
|
||||
err_ue_id=ue["ue_id"],
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
return 0.0 # ?
|
||||
|
||||
|
||||
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple[np.ndarray, dict]:
|
||||
"""Calcule la matrice des notes du semestre
|
||||
@ -165,3 +213,29 @@ def notes_sem_assemble_matrix(modimpls_notes: list[pd.Series]) -> np.ndarray:
|
||||
modimpls_notes = np.stack(modimpls_notes_arr)
|
||||
# passe de (mod x etud) à (etud x mod)
|
||||
return modimpls_notes.T
|
||||
|
||||
|
||||
def comp_etud_sum_coef_modules_ue(formsemestre_id, etudid, ue_id):
|
||||
"""Somme des coefficients des modules de l'UE dans lesquels cet étudiant est inscrit
|
||||
ou None s'il n'y a aucun module.
|
||||
"""
|
||||
# comme l'ancien notes_table.comp_etud_sum_coef_modules_ue
|
||||
# mais en raw sqlalchemy et la somme en SQL
|
||||
sql = text(
|
||||
"""
|
||||
SELECT sum(mod.coefficient)
|
||||
FROM notes_modules mod, notes_moduleimpl mi, notes_moduleimpl_inscription ins
|
||||
WHERE mod.id = mi.module_id
|
||||
and ins.etudid = :etudid
|
||||
and ins.moduleimpl_id = mi.id
|
||||
and mi.formsemestre_id = :formsemestre_id
|
||||
and mod.ue_id = :ue_id
|
||||
"""
|
||||
)
|
||||
cursor = db.session.execute(
|
||||
sql, {"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id}
|
||||
)
|
||||
r = cursor.fetchone()
|
||||
if r is None:
|
||||
return None
|
||||
return r[0]
|
||||
|
@ -8,17 +8,22 @@ from collections import defaultdict, Counter
|
||||
from functools import cached_property
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from flask import g
|
||||
|
||||
from app.comp.aux_stats import StatsMoyenne
|
||||
from app.comp import moy_sem
|
||||
from app.comp.res_cache import ResultatsCache
|
||||
from app.comp import res_sem
|
||||
from app.comp.moy_mod import ModuleImplResults
|
||||
from app.models import FormSemestre, Identite, ModuleImpl
|
||||
from app.models import FormSemestreUECoef
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
# Il faut bien distinguer
|
||||
# - ce qui est caché de façon persistente (via redis):
|
||||
@ -53,6 +58,7 @@ class ResultatsSemestre(ResultatsCache):
|
||||
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
|
||||
self.etud_coef_ue_df = None
|
||||
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
|
||||
self.validations = None
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||
@ -155,6 +161,115 @@ class ResultatsSemestre(ResultatsCache):
|
||||
"""
|
||||
return self.etud_moy_ue > (seuil - scu.NOTES_TOLERANCE)
|
||||
|
||||
def apply_capitalisation(self):
|
||||
"""Recalcule la moyenne générale pour prendre en compte d'éventuelles
|
||||
UE capitalisées.
|
||||
"""
|
||||
# Supposant qu'il y a peu d'UE capitalisées,
|
||||
# on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée.
|
||||
# return # XXX XXX XXX
|
||||
if not self.validations:
|
||||
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||
ue_capitalisees = self.validations.ue_capitalisees
|
||||
ue_by_code = {}
|
||||
for etudid in ue_capitalisees.index:
|
||||
recompute_mg = False
|
||||
# ue_codes = set(ue_capitalisees.loc[etudid]["ue_code"])
|
||||
# for ue_code in ue_codes:
|
||||
# ue = ue_by_code.get(ue_code)
|
||||
# if ue is None:
|
||||
# ue = self.formsemestre.query_ues.filter_by(ue_code=ue_code)
|
||||
# ue_by_code[ue_code] = ue
|
||||
|
||||
# Quand il y a une capitalisation, vérifie toutes les UEs
|
||||
sum_notes_ue = 0.0
|
||||
sum_coefs_ue = 0.0
|
||||
for ue in self.formsemestre.query_ues():
|
||||
ue_cap = self.get_etud_ue_status(etudid, ue.id)
|
||||
if ue_cap["is_capitalized"]:
|
||||
recompute_mg = True
|
||||
coef = ue_cap["coef_ue"]
|
||||
if not np.isnan(ue_cap["moy"]):
|
||||
sum_notes_ue += ue_cap["moy"] * coef
|
||||
sum_coefs_ue += coef
|
||||
|
||||
if recompute_mg and sum_coefs_ue > 0.0:
|
||||
# On doit prendre en compte une ou plusieurs UE capitalisées
|
||||
# et donc recalculer la moyenne générale
|
||||
self.etud_moy_gen[etudid] = sum_notes_ue / sum_coefs_ue
|
||||
|
||||
def _get_etud_ue_cap(self, etudid, ue):
|
||||
""""""
|
||||
capitalisations = self.validations.ue_capitalisees.loc[etudid]
|
||||
if isinstance(capitalisations, pd.DataFrame):
|
||||
ue_cap = capitalisations[capitalisations["ue_code"] == ue.ue_code]
|
||||
if isinstance(ue_cap, pd.DataFrame) and not ue_cap.empty:
|
||||
# si plusieurs fois capitalisée, prend le max
|
||||
cap_idx = ue_cap["moy_ue"].values.argmax()
|
||||
ue_cap = ue_cap.iloc[cap_idx]
|
||||
else:
|
||||
if capitalisations["ue_code"] == ue.ue_code:
|
||||
ue_cap = capitalisations
|
||||
else:
|
||||
ue_cap = None
|
||||
return ue_cap
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
|
||||
"""L'état de l'UE pour cet étudiant.
|
||||
Result: dict
|
||||
"""
|
||||
if not self.validations:
|
||||
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||
ue = UniteEns.query.get(ue_id) # TODO cacher nos UEs ?
|
||||
cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
|
||||
moy_ue = cur_moy_ue
|
||||
is_capitalized = False
|
||||
if etudid in self.validations.ue_capitalisees.index:
|
||||
ue_cap = self._get_etud_ue_cap(etudid, ue)
|
||||
if ue_cap is not None and not ue_cap.empty:
|
||||
if ue_cap["moy_ue"] > cur_moy_ue:
|
||||
moy_ue = ue_cap["moy_ue"]
|
||||
is_capitalized = True
|
||||
if is_capitalized:
|
||||
coef_ue = 1.0
|
||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||
|
||||
return {
|
||||
"is_capitalized": is_capitalized,
|
||||
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
|
||||
"coef_ue": coef_ue,
|
||||
"cur_moy_ue": cur_moy_ue,
|
||||
"moy": moy_ue,
|
||||
"event_date": ue_cap["event_date"] if is_capitalized else None,
|
||||
"ue": ue.to_dict(),
|
||||
"formsemestre_id": ue_cap["formsemestre_id"] if is_capitalized else None,
|
||||
"capitalized_ue_id": ue_cap["ue_id"] if is_capitalized else None,
|
||||
}
|
||||
|
||||
def get_etud_ue_cap_coef(self, etudid, ue, ue_cap):
|
||||
"""Calcule le coefficient d'une UE capitalisée, pour cet étudiant,
|
||||
injectée dans le semestre courant.
|
||||
|
||||
ue : ue du semestre courant
|
||||
|
||||
ue_cap = resultat de formsemestre_get_etud_capitalisation
|
||||
{ 'ue_id' (dans le semestre source),
|
||||
'ue_code', 'moy', 'event_date','formsemestre_id' }
|
||||
"""
|
||||
# 1- Coefficient explicitement déclaré dans le semestre courant pour cette UE ?
|
||||
ue_coef_db = FormSemestreUECoef.query.filter_by(
|
||||
formsemestre_id=self.formsemestre.id, ue_id=ue.id
|
||||
).first()
|
||||
if ue_coef_db is not None:
|
||||
return ue_coef_db.coefficient
|
||||
|
||||
# En APC: somme des coefs des modules vers cette UE
|
||||
# En classique: Capitalisation UE externe: quel coef appliquer ?
|
||||
# En ScoDoc 7, calculait la somme des coefs dans l'UE du semestre d'origine
|
||||
# ici si l'étudiant est inscrit dans le semestre courant,
|
||||
# somme des coefs des modules de l'UE auxquels il est inscrit
|
||||
return self.compute_etud_ue_coef(etudid, ue)
|
||||
|
||||
|
||||
# Pour raccorder le code des anciens codes qui attendent une NoteTable
|
||||
class NotesTableCompat(ResultatsSemestre):
|
||||
@ -189,7 +304,6 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
self.moy_moy = "NA"
|
||||
self.expr_diagnostics = ""
|
||||
self.parcours = self.formsemestre.formation.get_parcours()
|
||||
self.validations = None
|
||||
|
||||
def get_etudids(self, sorted=False) -> list[int]:
|
||||
"""Liste des etudids inscrits, incluant les démissionnaires.
|
||||
@ -353,16 +467,6 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"ects_fond": 0.0, # not implemented (anciennemment pour école ingé)
|
||||
}
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||
return {
|
||||
"is_capitalized": False, # XXX TODO
|
||||
"is_external": False, # XXX TODO
|
||||
"coef_ue": coef_ue, # XXX TODO
|
||||
"cur_moy_ue": self.etud_moy_ue[ue_id][etudid],
|
||||
"moy": self.etud_moy_ue[ue_id][etudid],
|
||||
}
|
||||
|
||||
def get_etud_rang(self, etudid: int):
|
||||
return self.etud_moy_gen_ranks.get(etudid, 99999) # XXX
|
||||
|
||||
|
@ -427,10 +427,12 @@ class FormSemestreUECoef(db.Model):
|
||||
formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
index=True,
|
||||
)
|
||||
ue_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_ue.id"),
|
||||
index=True,
|
||||
)
|
||||
coefficient = db.Column(db.Float, nullable=False)
|
||||
|
||||
|
@ -703,11 +703,12 @@ class NotesTable:
|
||||
où ue_status = {
|
||||
'est_inscrit' : True si étudiant inscrit à au moins un module de cette UE
|
||||
'moy' : moyenne, avec capitalisation eventuelle
|
||||
'capitalized_ue_id' : id de l'UE capitalisée
|
||||
'coef_ue' : coef de l'UE utilisé pour le calcul de la moyenne générale
|
||||
(la somme des coefs des modules, ou le coef d'UE capitalisée,
|
||||
ou encore le coef d'UE si l'option use_ue_coefs est active)
|
||||
'cur_moy_ue' : moyenne de l'UE en cours (sans considérer de capitalisation)
|
||||
'cur_coef_ue': coefficient de l'UE courante
|
||||
'cur_coef_ue': coefficient de l'UE courante (inutilisé ?)
|
||||
'is_capitalized' : True|False,
|
||||
'ects_pot' : (float) nb de crédits ECTS qui seraient validés (sous réserve de validation par le jury),
|
||||
'ects_pot_fond': 0. si UE non fondamentale, = ects_pot sinon,
|
||||
|
@ -165,10 +165,7 @@ def _comp_ects_capitalises_by_ue_code(nt, etudid):
|
||||
for ue in ues:
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
if ue_status["is_capitalized"]:
|
||||
try:
|
||||
ects_val = float(ue_status["ue"]["ects"])
|
||||
except (ValueError, TypeError):
|
||||
ects_val = 0.0
|
||||
ects_val = float(ue_status["ue"]["ects"] or 0.0)
|
||||
ects_by_ue_code[ue["ue_code"]] = ects_val
|
||||
|
||||
return ects_by_ue_code
|
||||
|
Loading…
Reference in New Issue
Block a user