Compare commits
9 Commits
9b9d7b611b
...
0bf0311f2f
Author | SHA1 | Date | |
---|---|---|---|
0bf0311f2f | |||
5bbdc567f3 | |||
027f11e494 | |||
ef171364a6 | |||
2915f4e981 | |||
af659d5f09 | |||
838ae7cf7e | |||
e4c8637c41 | |||
0bf3c22cd0 |
|
@ -374,114 +374,115 @@ def formsemestre_etudiants(
|
||||||
return sorted(etuds, key=itemgetter("sort_key"))
|
return sorted(etuds, key=itemgetter("sort_key"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
# retrait (temporaire ? à discuter)
|
||||||
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
# @bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||||
@login_required
|
# @api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||||
@scodoc
|
# @login_required
|
||||||
@permission_required(Permission.ScoView)
|
# @scodoc
|
||||||
@as_json
|
# @permission_required(Permission.ScoView)
|
||||||
def etat_evals(formsemestre_id: int):
|
# @as_json
|
||||||
"""
|
# def etat_evals(formsemestre_id: int):
|
||||||
Informations sur l'état des évaluations d'un formsemestre.
|
# """
|
||||||
|
# Informations sur l'état des évaluations d'un formsemestre.
|
||||||
|
|
||||||
formsemestre_id : l'id d'un semestre
|
# formsemestre_id : l'id d'un semestre
|
||||||
|
|
||||||
Exemple de résultat :
|
# Exemple de résultat :
|
||||||
[
|
# [
|
||||||
{
|
# {
|
||||||
"id": 1, // moduleimpl_id
|
# "id": 1, // moduleimpl_id
|
||||||
"titre": "Initiation aux réseaux informatiques",
|
# "titre": "Initiation aux réseaux informatiques",
|
||||||
"evaluations": [
|
# "evaluations": [
|
||||||
{
|
# {
|
||||||
"id": 1,
|
# "id": 1,
|
||||||
"description": null,
|
# "description": null,
|
||||||
"datetime_epreuve": null,
|
# "datetime_epreuve": null,
|
||||||
"heure_fin": "09:00:00",
|
# "heure_fin": "09:00:00",
|
||||||
"coefficient": "02.00"
|
# "coefficient": "02.00"
|
||||||
"is_complete": true,
|
# "is_complete": true,
|
||||||
"nb_inscrits": 16,
|
# "nb_inscrits": 16,
|
||||||
"nb_manquantes": 0,
|
# "nb_manquantes": 0,
|
||||||
"ABS": 0,
|
# "ABS": 0,
|
||||||
"ATT": 0,
|
# "ATT": 0,
|
||||||
"EXC": 0,
|
# "EXC": 0,
|
||||||
"saisie_notes": {
|
# "saisie_notes": {
|
||||||
"datetime_debut": "2021-09-11T00:00:00+02:00",
|
# "datetime_debut": "2021-09-11T00:00:00+02:00",
|
||||||
"datetime_fin": "2022-08-25T00:00:00+02:00",
|
# "datetime_fin": "2022-08-25T00:00:00+02:00",
|
||||||
"datetime_mediane": "2022-03-19T00:00:00+01:00"
|
# "datetime_mediane": "2022-03-19T00:00:00+01:00"
|
||||||
}
|
# }
|
||||||
},
|
# },
|
||||||
...
|
# ...
|
||||||
]
|
# ]
|
||||||
},
|
# },
|
||||||
]
|
# ]
|
||||||
"""
|
# """
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
# query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
if g.scodoc_dept:
|
# if g.scodoc_dept:
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
# query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
# formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
app.set_sco_dept(formsemestre.departement.acronym)
|
# app.set_sco_dept(formsemestre.departement.acronym)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
# nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
result = []
|
# result = []
|
||||||
for modimpl_id in nt.modimpls_results:
|
# for modimpl_id in nt.modimpls_results:
|
||||||
modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl_id]
|
# modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl_id]
|
||||||
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(modimpl_id)
|
# modimpl: ModuleImpl = ModuleImpl.query.get_or_404(modimpl_id)
|
||||||
modimpl_dict = modimpl.to_dict(convert_objects=True)
|
# modimpl_dict = modimpl.to_dict(convert_objects=True)
|
||||||
|
|
||||||
list_eval = []
|
# list_eval = []
|
||||||
for evaluation_id in modimpl_results.evaluations_etat:
|
# for evaluation_id in modimpl_results.evaluations_etat:
|
||||||
eval_etat = modimpl_results.evaluations_etat[evaluation_id]
|
# eval_etat = modimpl_results.evaluations_etat[evaluation_id]
|
||||||
evaluation = Evaluation.query.get_or_404(evaluation_id)
|
# evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||||
eval_dict = evaluation.to_dict_api()
|
# eval_dict = evaluation.to_dict_api()
|
||||||
eval_dict["etat"] = eval_etat.to_dict()
|
# eval_dict["etat"] = eval_etat.to_dict()
|
||||||
|
|
||||||
eval_dict["nb_inscrits"] = modimpl_results.nb_inscrits_module
|
# eval_dict["nb_inscrits"] = modimpl_results.nb_inscrits_module
|
||||||
eval_dict["nb_notes_manquantes"] = len(
|
# eval_dict["nb_notes_manquantes"] = len(
|
||||||
modimpl_results.evals_etudids_sans_note[evaluation.id]
|
# modimpl_results.evals_etudids_sans_note[evaluation.id]
|
||||||
)
|
# )
|
||||||
eval_dict["nb_notes_abs"] = sum(
|
# eval_dict["nb_notes_abs"] = sum(
|
||||||
modimpl_results.evals_notes[evaluation.id] == scu.NOTES_ABSENCE
|
# modimpl_results.evals_notes[evaluation.id] == scu.NOTES_ABSENCE
|
||||||
)
|
# )
|
||||||
eval_dict["nb_notes_att"] = eval_etat.nb_attente
|
# eval_dict["nb_notes_att"] = eval_etat.nb_attente
|
||||||
eval_dict["nb_notes_exc"] = sum(
|
# eval_dict["nb_notes_exc"] = sum(
|
||||||
modimpl_results.evals_notes[evaluation.id] == scu.NOTES_NEUTRALISE
|
# modimpl_results.evals_notes[evaluation.id] == scu.NOTES_NEUTRALISE
|
||||||
)
|
# )
|
||||||
|
|
||||||
# Récupération de toutes les notes de l'évaluation
|
# # Récupération de toutes les notes de l'évaluation
|
||||||
# eval["notes"] = modimpl_results.get_eval_notes_dict(evaluation_id)
|
# # eval["notes"] = modimpl_results.get_eval_notes_dict(evaluation_id)
|
||||||
|
|
||||||
notes = NotesNotes.query.filter_by(evaluation_id=evaluation.id).all()
|
# notes = NotesNotes.query.filter_by(evaluation_id=evaluation.id).all()
|
||||||
|
|
||||||
date_debut = None
|
# date_debut = None
|
||||||
date_fin = None
|
# date_fin = None
|
||||||
date_mediane = None
|
# date_mediane = None
|
||||||
|
|
||||||
# Si il y a plus d'une note saisie pour l'évaluation
|
# # Si il y a plus d'une note saisie pour l'évaluation
|
||||||
if len(notes) >= 1:
|
# if len(notes) >= 1:
|
||||||
# Tri des notes en fonction de leurs dates
|
# # Tri des notes en fonction de leurs dates
|
||||||
notes_sorted = sorted(notes, key=attrgetter("date"))
|
# notes_sorted = sorted(notes, key=attrgetter("date"))
|
||||||
|
|
||||||
date_debut = notes_sorted[0].date
|
# date_debut = notes_sorted[0].date
|
||||||
date_fin = notes_sorted[-1].date
|
# date_fin = notes_sorted[-1].date
|
||||||
|
|
||||||
# Note médiane
|
# # Note médiane
|
||||||
date_mediane = notes_sorted[len(notes_sorted) // 2].date
|
# date_mediane = notes_sorted[len(notes_sorted) // 2].date
|
||||||
|
|
||||||
eval_dict["saisie_notes"] = {
|
# eval_dict["saisie_notes"] = {
|
||||||
"datetime_debut": date_debut.isoformat()
|
# "datetime_debut": date_debut.isoformat()
|
||||||
if date_debut is not None
|
# if date_debut is not None
|
||||||
else None,
|
# else None,
|
||||||
"datetime_fin": date_fin.isoformat() if date_fin is not None else None,
|
# "datetime_fin": date_fin.isoformat() if date_fin is not None else None,
|
||||||
"datetime_mediane": date_mediane.isoformat()
|
# "datetime_mediane": date_mediane.isoformat()
|
||||||
if date_mediane is not None
|
# if date_mediane is not None
|
||||||
else None,
|
# else None,
|
||||||
}
|
# }
|
||||||
|
|
||||||
list_eval.append(eval_dict)
|
# list_eval.append(eval_dict)
|
||||||
|
|
||||||
modimpl_dict["evaluations"] = list_eval
|
# modimpl_dict["evaluations"] = list_eval
|
||||||
result.append(modimpl_dict)
|
# result.append(modimpl_dict)
|
||||||
return result
|
# return result
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/resultats")
|
@bp.route("/formsemestre/<int:formsemestre_id>/resultats")
|
||||||
|
|
|
@ -417,7 +417,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||||
+ '</div><div class="warning">'.join(messages)
|
+ '</div><div class="warning">'.join(messages)
|
||||||
+ "</div>"
|
+ "</div>"
|
||||||
)
|
)
|
||||||
self.codes = [self.codes[0]] + sorted(self.codes[1:])
|
self.codes = [self.codes[0]] + sorted((c or "") for c in self.codes[1:])
|
||||||
|
|
||||||
def passage_de_droit_en_but3(self) -> tuple[bool, str]:
|
def passage_de_droit_en_but3(self) -> tuple[bool, str]:
|
||||||
"""Vérifie si les conditions supplémentaires de passage BUT2 vers BUT3 sont satisfaites"""
|
"""Vérifie si les conditions supplémentaires de passage BUT2 vers BUT3 sont satisfaites"""
|
||||||
|
|
|
@ -56,6 +56,7 @@ class EvaluationEtat:
|
||||||
|
|
||||||
evaluation_id: int
|
evaluation_id: int
|
||||||
nb_attente: int
|
nb_attente: int
|
||||||
|
nb_notes: int # nb notes d'étudiants inscrits au semestre et au modimpl
|
||||||
is_complete: bool
|
is_complete: bool
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
@ -168,13 +169,15 @@ class ModuleImplResults:
|
||||||
# NULL en base => ABS (= -999)
|
# NULL en base => ABS (= -999)
|
||||||
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
||||||
# Ce merge ne garde que les étudiants inscrits au module
|
# Ce merge ne garde que les étudiants inscrits au module
|
||||||
# et met à NULL les notes non présentes
|
# et met à NULL (NaN) les notes non présentes
|
||||||
# (notes non saisies ou etuds non inscrits au module):
|
# (notes non saisies ou etuds non inscrits au module):
|
||||||
evals_notes = evals_notes.merge(
|
evals_notes = evals_notes.merge(
|
||||||
eval_df, how="left", left_index=True, right_index=True
|
eval_df, how="left", left_index=True, right_index=True
|
||||||
)
|
)
|
||||||
# Notes en attente: (ne prend en compte que les inscrits, non démissionnaires)
|
# Notes en attente: (ne prend en compte que les inscrits, non démissionnaires)
|
||||||
eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)]
|
eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)]
|
||||||
|
# Nombre de notes (non vides, incluant ATT etc) des inscrits:
|
||||||
|
nb_notes = eval_notes_inscr.notna().sum()
|
||||||
eval_etudids_attente = set(
|
eval_etudids_attente = set(
|
||||||
eval_notes_inscr.iloc[
|
eval_notes_inscr.iloc[
|
||||||
(eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
|
(eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
|
||||||
|
@ -184,6 +187,7 @@ class ModuleImplResults:
|
||||||
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
||||||
evaluation_id=evaluation.id,
|
evaluation_id=evaluation.id,
|
||||||
nb_attente=len(eval_etudids_attente),
|
nb_attente=len(eval_etudids_attente),
|
||||||
|
nb_notes=nb_notes,
|
||||||
is_complete=is_complete,
|
is_complete=is_complete,
|
||||||
)
|
)
|
||||||
# au moins une note en ATT dans ce modimpl:
|
# au moins une note en ATT dans ce modimpl:
|
||||||
|
|
|
@ -9,12 +9,13 @@
|
||||||
|
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
import datetime
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import sqlalchemy as sa
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
|
@ -22,14 +23,19 @@ from app.comp import res_sem
|
||||||
from app.comp.res_cache import ResultatsCache
|
from app.comp.res_cache import ResultatsCache
|
||||||
from app.comp.jury import ValidationsSemestre
|
from app.comp.jury import ValidationsSemestre
|
||||||
from app.comp.moy_mod import ModuleImplResults
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
from app.models import FormSemestre, FormSemestreUECoef
|
from app.models import (
|
||||||
from app.models import Identite
|
Evaluation,
|
||||||
from app.models import ModuleImpl, ModuleImplInscription
|
FormSemestre,
|
||||||
from app.models import ScolarAutorisationInscription
|
FormSemestreUECoef,
|
||||||
from app.models.ues import UniteEns
|
Identite,
|
||||||
|
ModuleImpl,
|
||||||
|
ModuleImplInscription,
|
||||||
|
ScolarAutorisationInscription,
|
||||||
|
UniteEns,
|
||||||
|
)
|
||||||
from app.scodoc.sco_cache import ResultatsSemestreCache
|
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError, ScoTemporaryError
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
|
@ -192,16 +198,82 @@ class ResultatsSemestre(ResultatsCache):
|
||||||
*[mr.etudids_attente for mr in self.modimpls_results.values()]
|
*[mr.etudids_attente for mr in self.modimpls_results.values()]
|
||||||
)
|
)
|
||||||
|
|
||||||
# # Etat des évaluations
|
# Etat des évaluations
|
||||||
# # (se substitue à do_evaluation_etat, sans les moyennes par groupes)
|
def get_evaluation_etat(self, evaluation: Evaluation) -> dict:
|
||||||
# def get_evaluations_etats(evaluation_id: int) -> dict:
|
"""État d'une évaluation
|
||||||
# """Renvoie dict avec les clés:
|
{
|
||||||
# last_modif
|
"coefficient" : float, # 0 si None
|
||||||
# nb_evals_completes
|
"description" : str, # de l'évaluation, "" si None
|
||||||
# nb_evals_en_cours
|
"etat" {
|
||||||
# nb_evals_vides
|
"evalcomplete" : bool,
|
||||||
# attente
|
"last_modif" : datetime.datetime | None, # saisie de note la plus récente
|
||||||
# """
|
"nb_notes" : int, # nb notes d'étudiants inscrits
|
||||||
|
},
|
||||||
|
"evaluatiuon_id" : int,
|
||||||
|
"jour" : datetime.datetime, # e.date_debut or datetime.datetime(1900, 1, 1)
|
||||||
|
"publish_incomplete" : bool,
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
mod_results = self.modimpls_results.get(evaluation.moduleimpl_id)
|
||||||
|
if mod_results is None:
|
||||||
|
raise ScoTemporaryError() # argh !
|
||||||
|
etat = mod_results.evaluations_etat.get(evaluation.id)
|
||||||
|
if etat is None:
|
||||||
|
raise ScoTemporaryError() # argh !
|
||||||
|
# Date de dernière saisie de note
|
||||||
|
cursor = db.session.execute(
|
||||||
|
sa.text(
|
||||||
|
"SELECT MAX(date) FROM notes_notes WHERE evaluation_id = :evaluation_id"
|
||||||
|
),
|
||||||
|
{"evaluation_id": evaluation.id},
|
||||||
|
)
|
||||||
|
date_modif = cursor.one_or_none()
|
||||||
|
last_modif = date_modif[0] if date_modif else None
|
||||||
|
return {
|
||||||
|
"coefficient": evaluation.coefficient or 0.0,
|
||||||
|
"description": evaluation.description or "",
|
||||||
|
"evaluation_id": evaluation.id,
|
||||||
|
"jour": evaluation.date_debut or datetime.datetime(1900, 1, 1),
|
||||||
|
"etat": {
|
||||||
|
"evalcomplete": etat.is_complete,
|
||||||
|
"nb_notes": etat.nb_notes,
|
||||||
|
"last_modif": last_modif,
|
||||||
|
},
|
||||||
|
"publish_incomplete": evaluation.publish_incomplete,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_mod_evaluation_etat_list(self, modimpl: ModuleImpl) -> list[dict]:
|
||||||
|
"""Liste des états des évaluations de ce module
|
||||||
|
[ evaluation_etat, ... ] (voir get_evaluation_etat)
|
||||||
|
trié par (numero desc, date_debut desc)
|
||||||
|
"""
|
||||||
|
# nouvelle version 2024-02-02
|
||||||
|
return list(
|
||||||
|
reversed(
|
||||||
|
[
|
||||||
|
self.get_evaluation_etat(evaluation)
|
||||||
|
for evaluation in modimpl.evaluations
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# modernisation de get_mod_evaluation_etat_list
|
||||||
|
# utilisé par:
|
||||||
|
# sco_evaluations.do_evaluation_etat_in_mod
|
||||||
|
# e["etat"]["evalcomplete"]
|
||||||
|
# e["etat"]["nb_notes"]
|
||||||
|
# e["etat"]["last_modif"]
|
||||||
|
#
|
||||||
|
# sco_formsemestre_status.formsemestre_description_table
|
||||||
|
# "jour" (qui est e.date_debut or datetime.date(1900, 1, 1))
|
||||||
|
# "description"
|
||||||
|
# "coefficient"
|
||||||
|
# e["etat"]["evalcomplete"]
|
||||||
|
# publish_incomplete
|
||||||
|
#
|
||||||
|
# sco_formsemestre_status.formsemestre_tableau_modules
|
||||||
|
# e["etat"]["nb_notes"]
|
||||||
|
#
|
||||||
|
|
||||||
# --- JURY...
|
# --- JURY...
|
||||||
def get_formsemestre_validations(self) -> ValidationsSemestre:
|
def get_formsemestre_validations(self) -> ValidationsSemestre:
|
||||||
|
|
|
@ -423,30 +423,37 @@ class NotesTableCompat(ResultatsSemestre):
|
||||||
)
|
)
|
||||||
return evaluations
|
return evaluations
|
||||||
|
|
||||||
def get_evaluations_etats(self) -> list[dict]:
|
def get_evaluations_etats(self) -> dict[int, dict]:
|
||||||
"""Liste de toutes les évaluations du semestre
|
""" "état" de chaque évaluation du semestre
|
||||||
[ {...evaluation et son etat...} ]"""
|
{
|
||||||
# TODO: à moderniser (voir dans ResultatsSemestre)
|
evaluation_id : {
|
||||||
# utilisé par
|
"evalcomplete" : bool,
|
||||||
# do_evaluation_etat_in_sem
|
"last_modif" : datetime | None
|
||||||
|
"nb_notes" : int,
|
||||||
|
}, ...
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# utilisé par do_evaluation_etat_in_sem
|
||||||
|
evaluations_etats = {}
|
||||||
|
for modimpl in self.formsemestre.modimpls_sorted:
|
||||||
|
for evaluation in modimpl.evaluations:
|
||||||
|
evaluation_etat = self.get_evaluation_etat(evaluation)
|
||||||
|
evaluations_etats[evaluation.id] = evaluation_etat["etat"]
|
||||||
|
return evaluations_etats
|
||||||
|
|
||||||
from app.scodoc import sco_evaluations
|
# ancienne version < 2024-02-02
|
||||||
|
# def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
|
||||||
if not hasattr(self, "_evaluations_etats"):
|
# """Liste des états des évaluations de ce module
|
||||||
self._evaluations_etats = sco_evaluations.do_evaluation_list_in_sem(
|
# ordonnée selon (numero desc, date_debut desc)
|
||||||
self.formsemestre.id
|
# """
|
||||||
)
|
# # à moderniser: lent, recharge des données que l'on a déjà...
|
||||||
|
# # remplacemé par ResultatsSemestre.get_mod_evaluation_etat_list
|
||||||
return self._evaluations_etats
|
# #
|
||||||
|
# return [
|
||||||
def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
|
# e
|
||||||
"""Liste des états des évaluations de ce module"""
|
# for e in self.get_evaluations_etats()
|
||||||
# XXX TODO à moderniser: lent, recharge des données que l'on a déjà...
|
# if e["moduleimpl_id"] == moduleimpl_id
|
||||||
return [
|
# ]
|
||||||
e
|
|
||||||
for e in self.get_evaluations_etats()
|
|
||||||
if e["moduleimpl_id"] == moduleimpl_id
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_moduleimpls_attente(self):
|
def get_moduleimpls_attente(self):
|
||||||
"""Liste des modimpls du semestre ayant des notes en attente"""
|
"""Liste des modimpls du semestre ayant des notes en attente"""
|
||||||
|
|
|
@ -19,7 +19,7 @@ from app.models.departements import Departement
|
||||||
from app.models.scolar_event import ScolarEvent
|
from app.models.scolar_event import ScolarEvent
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc.sco_bac import Baccalaureat
|
from app.scodoc.sco_bac import Baccalaureat
|
||||||
from app.scodoc.sco_exceptions import ScoInvalidParamError, ScoValueError
|
from app.scodoc.sco_exceptions import ScoGenError, ScoInvalidParamError, ScoValueError
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
|
@ -233,6 +233,15 @@ class Identite(models.ScoDocModel):
|
||||||
log(f"Identite.create {etud}")
|
log(f"Identite.create {etud}")
|
||||||
return etud
|
return etud
|
||||||
|
|
||||||
|
def from_dict(self, args, **kwargs) -> bool:
|
||||||
|
"""Check arguments, then modify.
|
||||||
|
Add to session but don't commit.
|
||||||
|
True if modification.
|
||||||
|
"""
|
||||||
|
check_etud_duplicate_code(args, "code_nip")
|
||||||
|
check_etud_duplicate_code(args, "code_ine")
|
||||||
|
return super().from_dict(args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
||||||
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded."""
|
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded."""
|
||||||
|
@ -728,6 +737,60 @@ class Identite(models.ScoDocModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_etud_duplicate_code(args, code_name, edit=True):
|
||||||
|
"""Vérifie que le code n'est pas dupliqué.
|
||||||
|
Raises ScoGenError si problème.
|
||||||
|
"""
|
||||||
|
etudid = args.get("etudid", None)
|
||||||
|
if not args.get(code_name, None):
|
||||||
|
return
|
||||||
|
etuds = Identite.query.filter_by(
|
||||||
|
**{code_name: str(args[code_name]), "dept_id": g.scodoc_dept_id}
|
||||||
|
).all()
|
||||||
|
duplicate = False
|
||||||
|
if edit:
|
||||||
|
duplicate = (len(etuds) > 1) or (
|
||||||
|
(len(etuds) == 1) and etuds[0].id != args["etudid"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
duplicate = len(etuds) > 0
|
||||||
|
if duplicate:
|
||||||
|
listh = [] # liste des doubles
|
||||||
|
for etud in etuds:
|
||||||
|
listh.append(f"Autre étudiant: {etud.html_link_fiche()}")
|
||||||
|
if etudid:
|
||||||
|
submit_label = "retour à la fiche étudiant"
|
||||||
|
dest_endpoint = "scolar.fiche_etud"
|
||||||
|
parameters = {"etudid": etudid}
|
||||||
|
else:
|
||||||
|
if "tf_submitted" in args:
|
||||||
|
del args["tf_submitted"]
|
||||||
|
submit_label = "Continuer"
|
||||||
|
dest_endpoint = "scolar.etudident_create_form"
|
||||||
|
parameters = args
|
||||||
|
else:
|
||||||
|
submit_label = "Annuler"
|
||||||
|
dest_endpoint = "notes.index_html"
|
||||||
|
parameters = {}
|
||||||
|
|
||||||
|
err_page = f"""<h3><h3>Code étudiant ({code_name}) dupliqué !</h3>
|
||||||
|
<p class="help">Le {code_name} {args[code_name]} est déjà utilisé: un seul étudiant peut avoir
|
||||||
|
ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.
|
||||||
|
</p>
|
||||||
|
<ul><li>
|
||||||
|
{ '</li><li>'.join(listh) }
|
||||||
|
</li></ul>
|
||||||
|
<p>
|
||||||
|
<a href="{ url_for(dest_endpoint, scodoc_dept=g.scodoc_dept, **parameters) }
|
||||||
|
">{submit_label}</a>
|
||||||
|
</p>
|
||||||
|
"""
|
||||||
|
|
||||||
|
log(f"*** error: code {code_name} duplique: {args[code_name]}")
|
||||||
|
|
||||||
|
raise ScoGenError(err_page)
|
||||||
|
|
||||||
|
|
||||||
def make_etud_args(
|
def make_etud_args(
|
||||||
etudid=None, code_nip=None, use_request=True, raise_exc=False, abort_404=True
|
etudid=None, code_nip=None, use_request=True, raise_exc=False, abort_404=True
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
|
|
@ -349,8 +349,8 @@ class EtudiantsJuryPE:
|
||||||
trajectoire = trajectoire_aggr[aggregat]
|
trajectoire = trajectoire_aggr[aggregat]
|
||||||
if trajectoire:
|
if trajectoire:
|
||||||
# Le semestre terminal de l'étudiant de l'aggrégat
|
# Le semestre terminal de l'étudiant de l'aggrégat
|
||||||
fid = trajectoire.semestre_final.formsemestre_id
|
fid = trajectoire.formsemestre_final.formsemestre_id
|
||||||
formsemestres_terminaux[fid] = trajectoire.semestre_final
|
formsemestres_terminaux[fid] = trajectoire.formsemestre_final
|
||||||
return formsemestres_terminaux
|
return formsemestres_terminaux
|
||||||
|
|
||||||
def get_formsemestres(self, semestres_recherches=None):
|
def get_formsemestres(self, semestres_recherches=None):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from app.comp import moy_sem
|
from app.comp import moy_sem
|
||||||
from app.pe.pe_tabletags import TableTag
|
from app.pe.pe_tabletags import TableTag, MoyenneTag
|
||||||
from app.pe.pe_etudiant import EtudiantsJuryPE
|
from app.pe.pe_etudiant import EtudiantsJuryPE
|
||||||
from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE
|
from app.pe.pe_trajectoire import Trajectoire, TrajectoiresJuryPE
|
||||||
from app.pe.pe_trajectoiretag import TrajectoireTag
|
from app.pe.pe_trajectoiretag import TrajectoireTag
|
||||||
|
@ -10,7 +10,6 @@ import numpy as np
|
||||||
|
|
||||||
|
|
||||||
class AggregatInterclasseTag(TableTag):
|
class AggregatInterclasseTag(TableTag):
|
||||||
|
|
||||||
# -------------------------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------------------------
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -30,24 +29,30 @@ class AggregatInterclasseTag(TableTag):
|
||||||
"""
|
"""
|
||||||
TableTag.__init__(self)
|
TableTag.__init__(self)
|
||||||
|
|
||||||
# Le nom
|
|
||||||
self.aggregat = nom_aggregat
|
self.aggregat = nom_aggregat
|
||||||
|
"""Aggrégat de l'interclassement"""
|
||||||
|
|
||||||
self.nom = self.get_repr()
|
self.nom = self.get_repr()
|
||||||
|
|
||||||
"""Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)""" # TODO
|
"""Les étudiants diplômés et leurs trajectoires (cf. trajectoires.suivis)""" # TODO
|
||||||
self.diplomes_ids = etudiants.etudiants_diplomes
|
self.diplomes_ids = etudiants.etudiants_diplomes
|
||||||
self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids}
|
self.etudiants_diplomes = {etudid for etudid in self.diplomes_ids}
|
||||||
# pour les exports sous forme de dataFrame
|
# pour les exports sous forme de dataFrame
|
||||||
self.etudiants = {etudid: etudiants.identites[etudid].etat_civil for etudid in self.diplomes_ids}
|
self.etudiants = {
|
||||||
|
etudid: etudiants.identites[etudid].etat_civil
|
||||||
|
for etudid in self.diplomes_ids
|
||||||
|
}
|
||||||
|
|
||||||
# Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat
|
# Les trajectoires (et leur version tagguées), en ne gardant que celles associées à l'aggrégat
|
||||||
self.trajectoires: dict[int, Trajectoire] = {}
|
self.trajectoires: dict[int, Trajectoire] = {}
|
||||||
|
"""Ensemble des trajectoires associées à l'aggrégat"""
|
||||||
for trajectoire_id in trajectoires_jury_pe.trajectoires:
|
for trajectoire_id in trajectoires_jury_pe.trajectoires:
|
||||||
trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id]
|
trajectoire = trajectoires_jury_pe.trajectoires[trajectoire_id]
|
||||||
if trajectoire_id[0] == nom_aggregat:
|
if trajectoire_id[0] == nom_aggregat:
|
||||||
self.trajectoires[trajectoire_id] = trajectoire
|
self.trajectoires[trajectoire_id] = trajectoire
|
||||||
|
|
||||||
self.trajectoires_taggues: dict[int, Trajectoire] = {}
|
self.trajectoires_taggues: dict[int, Trajectoire] = {}
|
||||||
|
"""Ensemble des trajectoires tagguées associées à l'aggrégat"""
|
||||||
for trajectoire_id in self.trajectoires:
|
for trajectoire_id in self.trajectoires:
|
||||||
self.trajectoires_taggues[trajectoire_id] = trajectoires_taggues[
|
self.trajectoires_taggues[trajectoire_id] = trajectoires_taggues[
|
||||||
trajectoire_id
|
trajectoire_id
|
||||||
|
@ -56,26 +61,27 @@ class AggregatInterclasseTag(TableTag):
|
||||||
# Les trajectoires suivies par les étudiants du jury, en ne gardant que
|
# Les trajectoires suivies par les étudiants du jury, en ne gardant que
|
||||||
# celles associées aux diplomés
|
# celles associées aux diplomés
|
||||||
self.suivi: dict[int, Trajectoire] = {}
|
self.suivi: dict[int, Trajectoire] = {}
|
||||||
|
"""Association entre chaque étudiant et la trajectoire tagguée à prendre en
|
||||||
|
compte pour l'aggrégat"""
|
||||||
for etudid in self.diplomes_ids:
|
for etudid in self.diplomes_ids:
|
||||||
self.suivi[etudid] = trajectoires_jury_pe.suivi[etudid][nom_aggregat]
|
self.suivi[etudid] = trajectoires_jury_pe.suivi[etudid][nom_aggregat]
|
||||||
|
|
||||||
|
|
||||||
self.tags_sorted = self.do_taglist()
|
self.tags_sorted = self.do_taglist()
|
||||||
"""Liste des tags (triés par ordre alphabétique)"""
|
"""Liste des tags (triés par ordre alphabétique)"""
|
||||||
|
|
||||||
# Construit la matrice de notes
|
# Construit la matrice de notes
|
||||||
self.notes = self.compute_notes_matrice()
|
self.notes = self.compute_notes_matrice()
|
||||||
|
"""Matrice des notes de l'aggrégat"""
|
||||||
|
|
||||||
# Synthétise les moyennes/classements par tag
|
# Synthétise les moyennes/classements par tag
|
||||||
self.moyennes_tags = {}
|
self.moyennes_tags: dict[str, MoyenneTag] = {}
|
||||||
for tag in self.tags_sorted:
|
for tag in self.tags_sorted:
|
||||||
moy_gen_tag = self.notes[tag]
|
moy_gen_tag = self.notes[tag]
|
||||||
self.moyennes_tags[tag] = self.comp_moy_et_stat(moy_gen_tag)
|
self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)
|
||||||
|
|
||||||
# Est significatif ? (aka a-t-il des tags et des notes)
|
# Est significatif ? (aka a-t-il des tags et des notes)
|
||||||
self.significatif = len(self.tags_sorted) > 0
|
self.significatif = len(self.tags_sorted) > 0
|
||||||
|
|
||||||
|
|
||||||
def get_repr(self) -> str:
|
def get_repr(self) -> str:
|
||||||
"""Une représentation textuelle"""
|
"""Une représentation textuelle"""
|
||||||
return f"Aggrégat {self.aggregat}"
|
return f"Aggrégat {self.aggregat}"
|
||||||
|
@ -118,7 +124,4 @@ class AggregatInterclasseTag(TableTag):
|
||||||
etudids_communs, tags_communs
|
etudids_communs, tags_communs
|
||||||
]
|
]
|
||||||
|
|
||||||
# Force les nan
|
|
||||||
df.fillna(np.nan)
|
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
|
@ -65,35 +65,15 @@ import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
class JuryPE(object):
|
class JuryPE(object):
|
||||||
"""Classe mémorisant toutes les informations nécessaires pour établir un jury de PE.
|
|
||||||
Modèle basé sur NotesTable.
|
|
||||||
|
|
||||||
Attributs :
|
|
||||||
|
|
||||||
* diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX)
|
|
||||||
* juryEtudDict : dictionnaire récapitulant les étudiants participant au jury PE (données administratives +
|
|
||||||
celles des semestres valides à prendre en compte permettant le calcul des moyennes ...
|
|
||||||
``{'etudid : { 'nom', 'prenom', 'civilite', 'diplome', '', }}``
|
|
||||||
a
|
|
||||||
Rq: il contient à la fois les étudiants qui vont être diplomés à la date prévue
|
|
||||||
et ceux qui sont éliminés (abandon, redoublement, ...) pour affichage alternatif
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Variables de classe décrivant les aggrégats, leur ordre d'apparition temporelle et
|
|
||||||
# leur affichage dans les avis latex
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------------------------------------------
|
|
||||||
def __init__(self, diplome):
|
def __init__(self, diplome):
|
||||||
"""
|
"""
|
||||||
Création d'une table PE sur la base d'un semestre selectionné. De ce semestre est déduit :
|
Classe mémorisant toutes les informations nécessaires pour établir un jury de PE, sur la base
|
||||||
|
d'une année de diplôme. De ce semestre est déduit :
|
||||||
1. l'année d'obtention du DUT,
|
1. l'année d'obtention du DUT,
|
||||||
2. tous les étudiants susceptibles à ce stade (au regard de leur parcours) d'être diplomés.
|
2. tous les étudiants susceptibles à ce stade (au regard de leur parcours) d'être diplomés.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sem_base: le FormSemestre donnant le semestre à la base du jury PE
|
diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX)
|
||||||
semBase: le dictionnaire sem donnant la base du jury (CB: TODO: A supprimer à long term)
|
|
||||||
meme_programme: si True, impose un même programme pour tous les étudiants participant au jury,
|
|
||||||
si False, permet des programmes differents
|
|
||||||
"""
|
"""
|
||||||
self.diplome = diplome
|
self.diplome = diplome
|
||||||
"L'année du diplome"
|
"L'année du diplome"
|
||||||
|
@ -101,7 +81,7 @@ class JuryPE(object):
|
||||||
self.nom_export_zip = f"Jury_PE_{self.diplome}"
|
self.nom_export_zip = f"Jury_PE_{self.diplome}"
|
||||||
"Nom du zip où ranger les fichiers générés"
|
"Nom du zip où ranger les fichiers générés"
|
||||||
|
|
||||||
# Chargement des étudiants à prendre en compte dans le jury
|
# Chargement des étudiants à prendre en compte Sydans le jury
|
||||||
pe_affichage.pe_print(
|
pe_affichage.pe_print(
|
||||||
f"""*** Recherche et chargement des étudiants diplômés en {
|
f"""*** Recherche et chargement des étudiants diplômés en {
|
||||||
self.diplome}"""
|
self.diplome}"""
|
||||||
|
@ -122,7 +102,6 @@ class JuryPE(object):
|
||||||
self._gen_xls_synthese_jury_par_tag(zipfile)
|
self._gen_xls_synthese_jury_par_tag(zipfile)
|
||||||
self._gen_xls_synthese_par_etudiant(zipfile)
|
self._gen_xls_synthese_par_etudiant(zipfile)
|
||||||
|
|
||||||
|
|
||||||
# Fin !!!! Tada :)
|
# Fin !!!! Tada :)
|
||||||
|
|
||||||
def _gen_xls_diplomes(self, zipfile: ZipFile):
|
def _gen_xls_diplomes(self, zipfile: ZipFile):
|
||||||
|
@ -356,7 +335,9 @@ class JuryPE(object):
|
||||||
|
|
||||||
for aggregat in aggregats:
|
for aggregat in aggregats:
|
||||||
# Le dictionnaire par défaut des moyennes
|
# Le dictionnaire par défaut des moyennes
|
||||||
donnees[etudid] |= get_defaut_dict_synthese_aggregat(aggregat, self.diplome)
|
donnees[etudid] |= get_defaut_dict_synthese_aggregat(
|
||||||
|
aggregat, self.diplome
|
||||||
|
)
|
||||||
|
|
||||||
# La trajectoire de l'étudiant sur l'aggrégat
|
# La trajectoire de l'étudiant sur l'aggrégat
|
||||||
trajectoire = self.trajectoires.suivi[etudid][aggregat]
|
trajectoire = self.trajectoires.suivi[etudid][aggregat]
|
||||||
|
@ -364,16 +345,17 @@ class JuryPE(object):
|
||||||
trajectoire_tagguee = self.trajectoires_tagguees[
|
trajectoire_tagguee = self.trajectoires_tagguees[
|
||||||
trajectoire.trajectoire_id
|
trajectoire.trajectoire_id
|
||||||
]
|
]
|
||||||
else:
|
if tag in trajectoire_tagguee.moyennes_tags:
|
||||||
trajectoire_tagguee = None
|
# La trajectoire tagguée
|
||||||
|
moy_trajectoire_tag = trajectoire_tagguee.moyennes_tags[tag]
|
||||||
# L'interclassement
|
if moy_trajectoire_tag.is_significatif():
|
||||||
interclass = self.interclassements_taggues[aggregat]
|
# L'interclassement
|
||||||
|
interclass = self.interclassements_taggues[aggregat]
|
||||||
# Injection des données dans un dictionnaire
|
|
||||||
donnees[etudid] |= get_dict_synthese_aggregat(aggregat, trajectoire_tagguee, interclass, etudid, tag, self.diplome)
|
|
||||||
|
|
||||||
|
|
||||||
|
# Injection des données dans un dictionnaire
|
||||||
|
donnees[etudid] |= get_dict_synthese_aggregat(
|
||||||
|
aggregat, trajectoire_tagguee, interclass, etudid, tag, self.diplome
|
||||||
|
)
|
||||||
|
|
||||||
# Fin de l'aggrégat
|
# Fin de l'aggrégat
|
||||||
# Construction du dataFrame
|
# Construction du dataFrame
|
||||||
|
@ -424,7 +406,9 @@ class JuryPE(object):
|
||||||
|
|
||||||
for aggregat in aggregats:
|
for aggregat in aggregats:
|
||||||
# Le dictionnaire par défaut des moyennes
|
# Le dictionnaire par défaut des moyennes
|
||||||
donnees[tag] |= get_defaut_dict_synthese_aggregat(aggregat, self.diplome)
|
donnees[tag] |= get_defaut_dict_synthese_aggregat(
|
||||||
|
aggregat, self.diplome
|
||||||
|
)
|
||||||
|
|
||||||
# La trajectoire de l'étudiant sur l'aggrégat
|
# La trajectoire de l'étudiant sur l'aggrégat
|
||||||
trajectoire = self.trajectoires.suivi[etudid][aggregat]
|
trajectoire = self.trajectoires.suivi[etudid][aggregat]
|
||||||
|
@ -432,26 +416,25 @@ class JuryPE(object):
|
||||||
trajectoire_tagguee = self.trajectoires_tagguees[
|
trajectoire_tagguee = self.trajectoires_tagguees[
|
||||||
trajectoire.trajectoire_id
|
trajectoire.trajectoire_id
|
||||||
]
|
]
|
||||||
else:
|
if tag in trajectoire_tagguee.moyennes_tags:
|
||||||
trajectoire_tagguee = None
|
# L'interclassement
|
||||||
|
interclass = self.interclassements_taggues[aggregat]
|
||||||
|
|
||||||
# L'interclassement
|
|
||||||
interclass = self.interclassements_taggues[aggregat]
|
|
||||||
|
|
||||||
# Injection des données dans un dictionnaire
|
|
||||||
donnees[tag] |= get_dict_synthese_aggregat(aggregat, trajectoire_tagguee, interclass, etudid, tag, self.diplome)
|
|
||||||
|
|
||||||
|
# Injection des données dans un dictionnaire
|
||||||
|
donnees[tag] |= get_dict_synthese_aggregat(
|
||||||
|
aggregat, trajectoire_tagguee, interclass, etudid, tag, self.diplome
|
||||||
|
)
|
||||||
|
|
||||||
# Fin de l'aggrégat
|
# Fin de l'aggrégat
|
||||||
# Construction du dataFrame
|
# Construction du dataFrame
|
||||||
df = pd.DataFrame.from_dict(donnees, orient="index")
|
df = pd.DataFrame.from_dict(donnees, orient="index")
|
||||||
|
|
||||||
# Tri par nom/prénom
|
# Tri par nom/prénom
|
||||||
df.sort_values(
|
df.sort_values(by=[("", "", "tag")], inplace=True)
|
||||||
by=[("", "", "tag")], inplace=True
|
|
||||||
)
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
|
def compute_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
|
||||||
"""Créé les semestres taggués, de type 'S1', 'S2', ..., pour un groupe d'étudiants donnés.
|
"""Créé les semestres taggués, de type 'S1', 'S2', ..., pour un groupe d'étudiants donnés.
|
||||||
Chaque semestre taggué est rattaché à l'un des FormSemestre faisant partie du cursus scolaire
|
Chaque semestre taggué est rattaché à l'un des FormSemestre faisant partie du cursus scolaire
|
||||||
|
@ -587,7 +570,7 @@ def get_dict_synthese_aggregat(
|
||||||
interclassement_taggue: AggregatInterclasseTag,
|
interclassement_taggue: AggregatInterclasseTag,
|
||||||
etudid: int,
|
etudid: int,
|
||||||
tag: str,
|
tag: str,
|
||||||
diplome: int
|
diplome: int,
|
||||||
):
|
):
|
||||||
"""Renvoie le dictionnaire (à intégrer au tableur excel de synthese)
|
"""Renvoie le dictionnaire (à intégrer au tableur excel de synthese)
|
||||||
traduisant les résultats (moy/class) d'un étudiant à une trajectoire tagguée associée
|
traduisant les résultats (moy/class) d'un étudiant à une trajectoire tagguée associée
|
||||||
|
@ -600,66 +583,43 @@ def get_dict_synthese_aggregat(
|
||||||
note = np.nan
|
note = np.nan
|
||||||
|
|
||||||
# Les données de la trajectoire tagguée pour le tag considéré
|
# Les données de la trajectoire tagguée pour le tag considéré
|
||||||
if trajectoire_tagguee and tag in trajectoire_tagguee.moyennes_tags:
|
moy_tag = trajectoire_tagguee.moyennes_tags[tag]
|
||||||
bilan = trajectoire_tagguee.moyennes_tags[tag]
|
|
||||||
|
|
||||||
# La moyenne de l'étudiant
|
# Les données de l'étudiant
|
||||||
note = TableTag.get_note_for_df(bilan, etudid)
|
note = moy_tag.get_note_for_df(etudid)
|
||||||
|
|
||||||
# Statistiques sur le groupe
|
classement = moy_tag.get_class_for_df(etudid)
|
||||||
if not pd.isna(note) and note != np.nan:
|
nmin = moy_tag.get_min_for_df()
|
||||||
# Les moyennes de cette trajectoire
|
nmax = moy_tag.get_max_for_df()
|
||||||
donnees |= {
|
nmoy = moy_tag.get_moy_for_df()
|
||||||
|
|
||||||
|
# Statistiques sur le groupe
|
||||||
|
if not pd.isna(note) and note != np.nan:
|
||||||
|
# Les moyennes de cette trajectoire
|
||||||
|
donnees |= {
|
||||||
(descr, "", "note"): note,
|
(descr, "", "note"): note,
|
||||||
(
|
(descr, NOM_STAT_GROUPE, "class."): classement,
|
||||||
descr,
|
(descr, NOM_STAT_GROUPE, "min"): nmin,
|
||||||
NOM_STAT_GROUPE,
|
(descr, NOM_STAT_GROUPE, "moy"): nmoy,
|
||||||
"class.",
|
(descr, NOM_STAT_GROUPE, "max"): nmax,
|
||||||
): TableTag.get_class_for_df(bilan, etudid),
|
|
||||||
(
|
|
||||||
descr,
|
|
||||||
NOM_STAT_GROUPE,
|
|
||||||
"min",
|
|
||||||
): TableTag.get_min_for_df(bilan),
|
|
||||||
(
|
|
||||||
descr,
|
|
||||||
NOM_STAT_GROUPE,
|
|
||||||
"moy",
|
|
||||||
): TableTag.get_moy_for_df(bilan),
|
|
||||||
(
|
|
||||||
descr,
|
|
||||||
NOM_STAT_GROUPE,
|
|
||||||
"max",
|
|
||||||
): TableTag.get_max_for_df(bilan),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# L'interclassement
|
# L'interclassement
|
||||||
if tag in interclassement_taggue.moyennes_tags:
|
moy_tag = interclassement_taggue.moyennes_tags[tag]
|
||||||
bilan = interclassement_taggue.moyennes_tags[tag]
|
|
||||||
|
|
||||||
if not pd.isna(note) and note != np.nan:
|
classement = moy_tag.get_class_for_df(etudid)
|
||||||
nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}"
|
nmin = moy_tag.get_min_for_df()
|
||||||
|
nmax = moy_tag.get_max_for_df()
|
||||||
|
nmoy = moy_tag.get_moy_for_df()
|
||||||
|
|
||||||
|
if not pd.isna(note) and note != np.nan:
|
||||||
|
nom_stat_promo = f"{NOM_STAT_PROMO} {diplome}"
|
||||||
|
|
||||||
|
donnees |= {
|
||||||
|
(descr, nom_stat_promo, "class."): classement,
|
||||||
|
(descr, nom_stat_promo, "min"): nmin,
|
||||||
|
(descr, nom_stat_promo, "moy"): nmoy,
|
||||||
|
(descr, nom_stat_promo, "max"): nmax,
|
||||||
|
}
|
||||||
|
|
||||||
donnees |= {
|
|
||||||
(
|
|
||||||
descr,
|
|
||||||
nom_stat_promo,
|
|
||||||
"class.",
|
|
||||||
): TableTag.get_class_for_df(bilan, etudid),
|
|
||||||
(
|
|
||||||
descr,
|
|
||||||
nom_stat_promo,
|
|
||||||
"min",
|
|
||||||
): TableTag.get_min_for_df(bilan),
|
|
||||||
(
|
|
||||||
descr,
|
|
||||||
nom_stat_promo,
|
|
||||||
"moy",
|
|
||||||
): TableTag.get_moy_for_df(bilan),
|
|
||||||
(
|
|
||||||
descr,
|
|
||||||
nom_stat_promo,
|
|
||||||
"max",
|
|
||||||
): TableTag.get_max_for_df(bilan),
|
|
||||||
}
|
|
||||||
return donnees
|
return donnees
|
||||||
|
|
|
@ -38,7 +38,7 @@ Created on Fri Sep 9 09:15:05 2016
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import app.pe.pe_etudiant
|
import app.pe.pe_etudiant
|
||||||
from app import db, log
|
from app import db, log, ScoValueError
|
||||||
from app.comp import res_sem, moy_ue, moy_sem
|
from app.comp import res_sem, moy_ue, moy_sem
|
||||||
from app.comp.moy_sem import comp_ranks_series
|
from app.comp.moy_sem import comp_ranks_series
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
|
@ -49,7 +49,7 @@ from app.models.moduleimpls import ModuleImpl
|
||||||
from app.scodoc import sco_tag_module
|
from app.scodoc import sco_tag_module
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
import app.pe.pe_affichage as pe_affichage
|
import app.pe.pe_affichage as pe_affichage
|
||||||
from app.pe.pe_tabletags import TableTag, TAGS_RESERVES
|
from app.pe.pe_tabletags import TableTag, TAGS_RESERVES, MoyenneTag
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,44 +94,51 @@ class SemestreTag(TableTag):
|
||||||
|
|
||||||
# Les tags :
|
# Les tags :
|
||||||
## Saisis par l'utilisateur
|
## Saisis par l'utilisateur
|
||||||
self.tags_personnalises = get_synthese_tags_personnalises_semestre(
|
tags_personnalises = get_synthese_tags_personnalises_semestre(
|
||||||
self.nt.formsemestre
|
self.nt.formsemestre
|
||||||
)
|
)
|
||||||
## Déduit des compétences
|
## Déduit des compétences
|
||||||
self.tags_competences = get_noms_competences_from_ues(self.nt.formsemestre)
|
dict_ues_competences = get_noms_competences_from_ues(self.nt.formsemestre)
|
||||||
|
|
||||||
# Supprime les doublons dans les tags
|
self.tags = (
|
||||||
tags_reserves = TAGS_RESERVES + list(self.tags_competences.values())
|
list(tags_personnalises.keys())
|
||||||
for tag in self.tags_personnalises:
|
+ list(dict_ues_competences.values())
|
||||||
if tag in tags_reserves:
|
+ ["but"]
|
||||||
del self.tags_personnalises[tag]
|
)
|
||||||
pe_affichage.pe_print(f"Supprime le tag {tag}")
|
"""Tags du semestre taggué"""
|
||||||
|
|
||||||
|
## Vérifie l'unicité des tags
|
||||||
|
if len(set(self.tags)) != len(self.tags):
|
||||||
|
raise ScoValueError(
|
||||||
|
f"""Erreur dans le module PE : L'un des tags saisis dans le programme
|
||||||
|
fait parti des tags réservés (par ex. "comp. <titre_compétence>"). Modifiez les
|
||||||
|
tags de votre programme"""
|
||||||
|
)
|
||||||
|
|
||||||
# Calcul des moyennes & les classements de chaque étudiant à chaque tag
|
# Calcul des moyennes & les classements de chaque étudiant à chaque tag
|
||||||
self.moyennes_tags = {}
|
self.moyennes_tags = {}
|
||||||
|
|
||||||
for tag in self.tags_personnalises:
|
for tag in tags_personnalises:
|
||||||
# pe_affichage.pe_print(f" -> Traitement du tag {tag}")
|
# pe_affichage.pe_print(f" -> Traitement du tag {tag}")
|
||||||
moy_gen_tag = self.compute_moyenne_tag(tag)
|
moy_gen_tag = self.compute_moyenne_tag(tag, tags_personnalises)
|
||||||
self.moyennes_tags[tag] = self.comp_moy_et_stat(moy_gen_tag)
|
self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)
|
||||||
|
|
||||||
# Ajoute les moyennes générales de BUT pour le semestre considéré
|
# Ajoute les moyennes générales de BUT pour le semestre considéré
|
||||||
moy_gen_but = self.nt.etud_moy_gen
|
moy_gen_but = self.nt.etud_moy_gen
|
||||||
moy_gen_but = pd.to_numeric(moy_gen_but, errors="coerce")
|
self.moyennes_tags["but"] = MoyenneTag("but", moy_gen_but)
|
||||||
self.moyennes_tags["but"] = self.comp_moy_et_stat(moy_gen_but)
|
|
||||||
|
|
||||||
# Ajoute les moyennes par compétence
|
# Ajoute les moyennes par compétence
|
||||||
for ue_id, competence in self.tags_competences.items():
|
for ue_id, competence in dict_ues_competences.items():
|
||||||
moy_ue = self.nt.etud_moy_ue[ue_id]
|
moy_ue = self.nt.etud_moy_ue[ue_id]
|
||||||
self.moyennes_tags[competence] = self.comp_moy_et_stat(moy_ue)
|
self.moyennes_tags[competence] = MoyenneTag(competence, moy_ue)
|
||||||
|
|
||||||
|
self.tags_sorted = self.get_all_tags()
|
||||||
|
"""Tags (personnalisés+compétences) par ordre alphabétique"""
|
||||||
|
|
||||||
# Synthétise l'ensemble des moyennes dans un dataframe
|
# Synthétise l'ensemble des moyennes dans un dataframe
|
||||||
self.tags_sorted = sorted(
|
|
||||||
self.moyennes_tags
|
self.notes = self.df_notes()
|
||||||
) # les tags (personnalisés+compétences) par ordre alphabétique
|
"""Dataframe synthétique des notes par tag"""
|
||||||
self.notes = (
|
|
||||||
self.df_notes()
|
|
||||||
) # Le dataframe synthétique des notes (=moyennes par tag)
|
|
||||||
|
|
||||||
pe_affichage.pe_print(
|
pe_affichage.pe_print(
|
||||||
f" => Traitement des tags {', '.join(self.tags_sorted)}"
|
f" => Traitement des tags {', '.join(self.tags_sorted)}"
|
||||||
|
@ -141,9 +148,10 @@ class SemestreTag(TableTag):
|
||||||
"""Nom affiché pour le semestre taggué"""
|
"""Nom affiché pour le semestre taggué"""
|
||||||
return app.pe.pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
|
return app.pe.pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
|
||||||
|
|
||||||
def compute_moyenne_tag(self, tag: str) -> list:
|
def compute_moyenne_tag(self, tag: str, tags_infos: dict) -> pd.Series:
|
||||||
"""Calcule la moyenne des étudiants pour le tag indiqué,
|
"""Calcule la moyenne des étudiants pour le tag indiqué,
|
||||||
pour ce SemestreTag.
|
pour ce SemestreTag, en ayant connaissance des informations sur
|
||||||
|
les tags (dictionnaire donnant les coeff de repondération)
|
||||||
|
|
||||||
Sont pris en compte les modules implémentés associés au tag,
|
Sont pris en compte les modules implémentés associés au tag,
|
||||||
avec leur éventuel coefficient de **repondération**, en utilisant les notes
|
avec leur éventuel coefficient de **repondération**, en utilisant les notes
|
||||||
|
@ -151,8 +159,8 @@ class SemestreTag(TableTag):
|
||||||
|
|
||||||
Force ou non le calcul de la moyenne lorsque des notes sont manquantes.
|
Force ou non le calcul de la moyenne lorsque des notes sont manquantes.
|
||||||
|
|
||||||
Renvoie les informations sous la forme d'une liste
|
Returns:
|
||||||
[ (moy, somme_coeff_normalise, etudid), ...]
|
La série des moyennes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""Adaptation du mask de calcul des moyennes au tag visé"""
|
"""Adaptation du mask de calcul des moyennes au tag visé"""
|
||||||
|
@ -163,13 +171,13 @@ class SemestreTag(TableTag):
|
||||||
|
|
||||||
"""Désactive tous les modules qui ne sont pas pris en compte pour ce tag"""
|
"""Désactive tous les modules qui ne sont pas pris en compte pour ce tag"""
|
||||||
for i, modimpl in enumerate(self.formsemestre.modimpls_sorted):
|
for i, modimpl in enumerate(self.formsemestre.modimpls_sorted):
|
||||||
if modimpl.moduleimpl_id not in self.tags_personnalises[tag]:
|
if modimpl.moduleimpl_id not in tags_infos[tag]:
|
||||||
modimpls_mask[i] = False
|
modimpls_mask[i] = False
|
||||||
|
|
||||||
"""Applique la pondération des coefficients"""
|
"""Applique la pondération des coefficients"""
|
||||||
modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy()
|
modimpl_coefs_ponderes_df = self.modimpl_coefs_df.copy()
|
||||||
for modimpl_id in self.tags_personnalises[tag]:
|
for modimpl_id in tags_infos[tag]:
|
||||||
ponderation = self.tags_personnalises[tag][modimpl_id]["ponderation"]
|
ponderation = tags_infos[tag][modimpl_id]["ponderation"]
|
||||||
modimpl_coefs_ponderes_df[modimpl_id] *= ponderation
|
modimpl_coefs_ponderes_df[modimpl_id] *= ponderation
|
||||||
|
|
||||||
"""Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)"""
|
"""Calcule les moyennes pour le tag visé dans chaque UE (dataframe etudid x ues)"""
|
||||||
|
|
|
@ -40,6 +40,7 @@ Created on Thu Sep 8 09:36:33 2016
|
||||||
import datetime
|
import datetime
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from app import ScoValueError
|
||||||
from app.comp.moy_sem import comp_ranks_series
|
from app.comp.moy_sem import comp_ranks_series
|
||||||
from app.pe import pe_affichage
|
from app.pe import pe_affichage
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
@ -48,63 +49,35 @@ import pandas as pd
|
||||||
|
|
||||||
TAGS_RESERVES = ["but"]
|
TAGS_RESERVES = ["but"]
|
||||||
|
|
||||||
|
class MoyenneTag():
|
||||||
|
|
||||||
class TableTag(object):
|
def __init__(self, tag: str, notes: pd.Series):
|
||||||
def __init__(self):
|
"""Classe centralisant la synthèse des moyennes/classements d'une série
|
||||||
"""Classe centralisant différentes méthodes communes aux
|
d'étudiants à un tag donné, en stockant un dictionnaire :
|
||||||
SemestreTag, TrajectoireTag, AggregatInterclassTag
|
|
||||||
|
``
|
||||||
|
{
|
||||||
|
"notes": la Serie pandas des notes (float),
|
||||||
|
"classements": la Serie pandas des classements (float),
|
||||||
|
"min": la note minimum,
|
||||||
|
"max": la note maximum,
|
||||||
|
"moy": la moyenne,
|
||||||
|
"nb_inscrits": le nombre d'étudiants ayant une note,
|
||||||
|
}
|
||||||
|
``
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tag: Un tag
|
||||||
|
note: Une série de notes (moyenne) sous forme d'un pd.Series()
|
||||||
"""
|
"""
|
||||||
pass
|
self.tag = tag
|
||||||
|
"""Le tag associé à la moyenne"""
|
||||||
|
self.synthese = self.comp_moy_et_stat(notes)
|
||||||
|
"""La synthèse des notes/classements/statistiques"""
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------
|
def __eq__(self, other):
|
||||||
def get_all_tags(self):
|
"""Egalité de deux MoyenneTag lorsque leur tag sont identiques"""
|
||||||
"""Liste des tags de la table, triée par ordre alphabétique
|
return self.tag == other.tag
|
||||||
|
|
||||||
Returns:
|
|
||||||
Liste de tags triés par ordre alphabétique
|
|
||||||
"""
|
|
||||||
return sorted(self.moyennes_tags.keys())
|
|
||||||
|
|
||||||
def df_moyennes_et_classements(self):
|
|
||||||
"""Renvoie un dataframe listant toutes les moyennes,
|
|
||||||
et les classements des étudiants pour tous les tags
|
|
||||||
"""
|
|
||||||
|
|
||||||
etudiants = self.etudiants
|
|
||||||
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
|
|
||||||
|
|
||||||
for tag in self.get_all_tags():
|
|
||||||
df = df.join(self.moyennes_tags[tag]["notes"].rename(f"Moy {tag}"))
|
|
||||||
df = df.join(self.moyennes_tags[tag]["classements"].rename(f"Class {tag}"))
|
|
||||||
|
|
||||||
return df
|
|
||||||
|
|
||||||
def df_notes(self):
|
|
||||||
"""Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Un dataframe etudids x tag (avec tag par ordre alphabétique)
|
|
||||||
"""
|
|
||||||
tags = self.get_all_tags()
|
|
||||||
if tags:
|
|
||||||
dict_series = {tag: self.moyennes_tags[tag]["notes"] for tag in tags}
|
|
||||||
df = pd.DataFrame(dict_series)
|
|
||||||
return df
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def str_tagtable(self):
|
|
||||||
"""Renvoie une chaine de caractère listant toutes les moyennes,
|
|
||||||
les rangs des étudiants pour tous les tags."""
|
|
||||||
|
|
||||||
etudiants = self.etudiants
|
|
||||||
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
|
|
||||||
|
|
||||||
for tag in self.get_all_tags():
|
|
||||||
df = df.join(self.moyennes_tags[tag]["notes"].rename(f"moy {tag}"))
|
|
||||||
df = df.join(self.moyennes_tags[tag]["classements"].rename(f"class {tag}"))
|
|
||||||
|
|
||||||
return df.to_csv(sep=";")
|
|
||||||
|
|
||||||
def comp_moy_et_stat(self, notes: pd.Series) -> dict:
|
def comp_moy_et_stat(self, notes: pd.Series) -> dict:
|
||||||
"""Calcule et structure les données nécessaires au PE pour une série
|
"""Calcule et structure les données nécessaires au PE pour une série
|
||||||
|
@ -131,7 +104,7 @@ class TableTag(object):
|
||||||
(_, class_gen_ue_non_nul) = comp_ranks_series(notes_non_nulles)
|
(_, class_gen_ue_non_nul) = comp_ranks_series(notes_non_nulles)
|
||||||
|
|
||||||
# Les classements (toutes notes confondues, avec NaN si pas de notes)
|
# Les classements (toutes notes confondues, avec NaN si pas de notes)
|
||||||
class_gen_ue = pd.Series(np.nan, index=notes.index, dtype="Int64")
|
class_gen_ue = pd.Series(np.nan, index=notes.index) #, dtype="Int64")
|
||||||
class_gen_ue[indices] = class_gen_ue_non_nul[indices]
|
class_gen_ue[indices] = class_gen_ue_non_nul[indices]
|
||||||
|
|
||||||
synthese = {
|
synthese = {
|
||||||
|
@ -140,42 +113,97 @@ class TableTag(object):
|
||||||
"min": notes.min(),
|
"min": notes.min(),
|
||||||
"max": notes.max(),
|
"max": notes.max(),
|
||||||
"moy": notes.mean(),
|
"moy": notes.mean(),
|
||||||
"nb_inscrits": len(indices),
|
"nb_inscrits": sum(indices),
|
||||||
}
|
}
|
||||||
return synthese
|
return synthese
|
||||||
|
|
||||||
@classmethod
|
def get_note_for_df(self, etudid: int):
|
||||||
def get_min_for_df(cls, bilan: dict) -> float:
|
"""Note d'un étudiant donné par son etudid"""
|
||||||
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné,
|
return round(self.synthese["notes"].loc[etudid], 2)
|
||||||
revoie le min renseigné pour affichage dans un df"""
|
|
||||||
return round(bilan["min"], 2)
|
|
||||||
|
|
||||||
@classmethod
|
def get_min_for_df(self) -> float:
|
||||||
def get_max_for_df(cls, bilan: dict) -> float:
|
"""Min renseigné pour affichage dans un df"""
|
||||||
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné,
|
return round(self.synthese["min"], 2)
|
||||||
renvoie le max renseigné pour affichage dans un df"""
|
|
||||||
return round(bilan["max"], 2)
|
|
||||||
|
|
||||||
@classmethod
|
def get_max_for_df(self) -> float:
|
||||||
def get_moy_for_df(cls, bilan: dict) -> float:
|
"""Max renseigné pour affichage dans un df"""
|
||||||
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné,
|
return round(self.synthese["max"], 2)
|
||||||
renvoie la moyenne renseignée pour affichage dans un df"""
|
|
||||||
return round(bilan["moy"], 2)
|
|
||||||
|
|
||||||
@classmethod
|
def get_moy_for_df(self) -> float:
|
||||||
def get_class_for_df(cls, bilan: dict, etudid: int) -> str:
|
"""Moyenne renseignée pour affichage dans un df"""
|
||||||
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné,
|
return round(self.synthese["moy"], 2)
|
||||||
renvoie le classement ramené au nombre d'inscrits,
|
|
||||||
|
def get_class_for_df(self, etudid: int) -> str:
|
||||||
|
"""Classement ramené au nombre d'inscrits,
|
||||||
pour un étudiant donné par son etudid"""
|
pour un étudiant donné par son etudid"""
|
||||||
classement = bilan['classements'].loc[etudid]
|
classement = self.synthese['classements'].loc[etudid]
|
||||||
|
nb_inscrit = self.synthese['nb_inscrits']
|
||||||
if not pd.isna(classement):
|
if not pd.isna(classement):
|
||||||
return f"{classement}/{bilan['nb_inscrits']}"
|
classement = int(classement)
|
||||||
|
return f"{classement}/{nb_inscrit}"
|
||||||
else:
|
else:
|
||||||
return pe_affichage.SANS_NOTE
|
return pe_affichage.SANS_NOTE
|
||||||
|
|
||||||
@classmethod
|
def is_significatif(self) -> bool:
|
||||||
def get_note_for_df(cls, bilan: dict, etudid: int):
|
"""Indique si la moyenne est significative (c'est-à-dire à des notes)"""
|
||||||
"""Partant d'un dictionnaire `bilan` généralement une moyennes_tags pour un tag donné,
|
return self.synthese["nb_inscrits"] > 0
|
||||||
renvoie la note (moyenne)
|
|
||||||
pour un étudiant donné par son etudid"""
|
class TableTag(object):
|
||||||
return round(bilan["notes"].loc[etudid], 2)
|
def __init__(self):
|
||||||
|
"""Classe centralisant différentes méthodes communes aux
|
||||||
|
SemestreTag, TrajectoireTag, AggregatInterclassTag
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------
|
||||||
|
def get_all_tags(self):
|
||||||
|
"""Liste des tags de la table, triée par ordre alphabétique,
|
||||||
|
extraite des clés du dictionnaire ``moyennes_tags`` connues (tags en doublon
|
||||||
|
possible).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Liste de tags triés par ordre alphabétique
|
||||||
|
"""
|
||||||
|
return sorted(list(self.moyennes_tags.keys()))
|
||||||
|
|
||||||
|
def df_moyennes_et_classements(self) -> pd.DataFrame:
|
||||||
|
"""Renvoie un dataframe listant toutes les moyennes,
|
||||||
|
et les classements des étudiants pour tous les tags.
|
||||||
|
|
||||||
|
Est utilisé pour afficher le détail d'un tableau taggué
|
||||||
|
(semestres, trajectoires ou aggrégat)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Le dataframe des notes et des classements
|
||||||
|
"""
|
||||||
|
|
||||||
|
etudiants = self.etudiants
|
||||||
|
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
|
||||||
|
|
||||||
|
tags_tries = self.get_all_tags()
|
||||||
|
for tag in tags_tries:
|
||||||
|
moy_tag = self.moyennes_tags[tag]
|
||||||
|
df = df.join(moy_tag.synthese["notes"].rename(f"Moy {tag}"))
|
||||||
|
df = df.join(moy_tag.synthese["classements"].rename(f"Class {tag}"))
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def df_notes(self) -> pd.DataFrame | None:
|
||||||
|
"""Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Un dataframe etudids x tag (avec tag par ordre alphabétique)
|
||||||
|
"""
|
||||||
|
tags_tries = self.get_all_tags()
|
||||||
|
if tags_tries:
|
||||||
|
dict_series = {}
|
||||||
|
for tag in tags_tries:
|
||||||
|
# Les moyennes associés au tag
|
||||||
|
moy_tag = self.moyennes_tags[tag]
|
||||||
|
dict_series[tag] = moy_tag.synthese["notes"]
|
||||||
|
df = pd.DataFrame(dict_series)
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,43 +6,44 @@ from app.pe.pe_etudiant import EtudiantsJuryPE, get_dernier_semestre_en_date
|
||||||
|
|
||||||
|
|
||||||
class Trajectoire:
|
class Trajectoire:
|
||||||
"""Modélise, pour un aggrégat visé (par ex: 'S2', '3S', '2A')
|
|
||||||
et un ensemble d'étudiants donnés,
|
|
||||||
la combinaison des formsemestres des étudiants amenant à un semestre
|
|
||||||
terminal visé.
|
|
||||||
|
|
||||||
Si l'aggrégat est un semestre de type Si, elle stocke le (ou les)
|
|
||||||
formsemestres de numéro i qu'ont suivis l'étudiant pour atteindre le Si
|
|
||||||
(en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants)
|
|
||||||
|
|
||||||
Pour des aggrégats de type iS ou iA (par ex, 3A=S1+S2+S3), elle identifie
|
|
||||||
les semestres que les étudiants ont suivis pour les amener jusqu'au semestre
|
|
||||||
terminal de la trajectoire (par ex: ici un S3).
|
|
||||||
Ces semestres peuvent être :
|
|
||||||
* des S1+S2+S1+S2+S3 si redoublement de la 1ère année
|
|
||||||
* des S1+S2+(année de césure)+S3 si césure, ...
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, nom_aggregat: str, semestre_final: FormSemestre):
|
def __init__(self, nom_aggregat: str, semestre_final: FormSemestre):
|
||||||
"""Modélise un ensemble de formsemestres d'étudiants
|
"""Modélise un ensemble de formsemestres d'étudiants
|
||||||
amenant à un semestre terminal
|
amenant à un semestre terminal, au sens d'un aggrégat (par ex: 'S2', '3S', '2A').
|
||||||
|
|
||||||
|
Si l'aggrégat est un semestre de type Si, elle stocke le (ou les)
|
||||||
|
formsemestres de numéro i qu'ont suivi l'étudiant pour atteindre le Si
|
||||||
|
(en général 1 si personnes n'a redoublé, mais 2 s'il y a des redoublants)
|
||||||
|
|
||||||
|
Pour des aggrégats de type iS ou iA (par ex, 3A=S1+S2+S3), elle identifie
|
||||||
|
les semestres que les étudiants ont suivis pour les amener jusqu'au semestre
|
||||||
|
terminal de la trajectoire (par ex: ici un S3).
|
||||||
|
|
||||||
|
Ces semestres peuvent être :
|
||||||
|
|
||||||
|
* des S1+S2+S1+S2+S3 si redoublement de la 1ère année
|
||||||
|
* des S1+S2+(année de césure)+S3 si césure, ...
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
nom_aggregat: Un nom d'aggrégat (par ex: '5S')
|
nom_aggregat: Un nom d'aggrégat (par ex: '5S')
|
||||||
semestre_final: Le semestre final de l'aggrégat
|
semestre_final: Le semestre final de l'aggrégat
|
||||||
"""
|
"""
|
||||||
self.nom = nom_aggregat
|
self.nom = nom_aggregat
|
||||||
self.semestre_final = semestre_final
|
"""Nom de l'aggrégat"""
|
||||||
self.trajectoire_id = (nom_aggregat, semestre_final.formsemestre_id)
|
|
||||||
|
self.formsemestre_final = semestre_final
|
||||||
|
"""FormSemestre terminal de la trajectoire"""
|
||||||
|
|
||||||
|
self.trajectoire_id = (nom_aggregat, semestre_final.formsemestre_id)
|
||||||
|
"""Identifiant de la trajectoire"""
|
||||||
|
|
||||||
"""Les semestres à aggréger"""
|
|
||||||
self.semestres_aggreges = {}
|
self.semestres_aggreges = {}
|
||||||
|
"""Semestres aggrégés"""
|
||||||
|
|
||||||
def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]):
|
def add_semestres_a_aggreger(self, semestres: dict[int:FormSemestre]):
|
||||||
"""Ajoute des semestres au semestre à aggréger
|
"""Ajout de semestres aux semestres à aggréger
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
semestres: Dictionnaire ``{fid: FormSemestre(fid)} à ajouter``
|
semestres: Dictionnaire ``{fid: FormSemestre(fid)}`` à ajouter
|
||||||
"""
|
"""
|
||||||
self.semestres_aggreges = self.semestres_aggreges | semestres
|
self.semestres_aggreges = self.semestres_aggreges | semestres
|
||||||
|
|
||||||
|
@ -55,27 +56,30 @@ class Trajectoire:
|
||||||
semestre = self.semestres_aggreges[fid]
|
semestre = self.semestres_aggreges[fid]
|
||||||
noms.append(f"S{semestre.semestre_id}({fid})")
|
noms.append(f"S{semestre.semestre_id}({fid})")
|
||||||
noms = sorted(noms)
|
noms = sorted(noms)
|
||||||
repr = f"{self.nom} ({self.semestre_final.formsemestre_id}) {self.semestre_final.date_fin.year}"
|
repr = f"{self.nom} ({self.formsemestre_final.formsemestre_id}) {self.formsemestre_final.date_fin.year}"
|
||||||
if verbose and noms:
|
if verbose and noms:
|
||||||
repr += " - " + "+".join(noms)
|
repr += " - " + "+".join(noms)
|
||||||
return repr
|
return repr
|
||||||
|
|
||||||
|
|
||||||
class TrajectoiresJuryPE:
|
class TrajectoiresJuryPE:
|
||||||
"""Centralise toutes les trajectoires du jury PE"""
|
|
||||||
|
|
||||||
def __init__(self, annee_diplome: int):
|
def __init__(self, annee_diplome: int):
|
||||||
"""
|
"""Classe centralisant toutes les trajectoires des étudiants à prendre
|
||||||
|
en compte dans un jury PE
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
annee_diplome: L'année de diplomation
|
annee_diplome: L'année de diplomation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.annee_diplome = annee_diplome
|
self.annee_diplome = annee_diplome
|
||||||
"""Toutes les trajectoires possibles"""
|
"""Toutes les trajectoires possibles"""
|
||||||
|
|
||||||
self.trajectoires: dict[tuple:Trajectoire] = {}
|
self.trajectoires: dict[tuple:Trajectoire] = {}
|
||||||
"""Quelle trajectoires pour quel étudiant :
|
"""Ensemble des trajectoires recensées : {(aggregat, fid_terminal): Trajectoire}"""
|
||||||
dictionnaire {etudid: {nom_aggregat: Trajectoire}}"""
|
|
||||||
self.suivi: dict[int:str] = {}
|
self.suivi: dict[int:str] = {}
|
||||||
|
"""Dictionnaire associant, pour chaque étudiant et pour chaque aggrégat,
|
||||||
|
sa trajectoire : {etudid: {nom_aggregat: Trajectoire}}"""
|
||||||
|
|
||||||
def cree_trajectoires(self, etudiants: EtudiantsJuryPE):
|
def cree_trajectoires(self, etudiants: EtudiantsJuryPE):
|
||||||
"""Créé toutes les trajectoires, au regard du cursus des étudiants
|
"""Créé toutes les trajectoires, au regard du cursus des étudiants
|
||||||
|
@ -122,9 +126,6 @@ class TrajectoiresJuryPE:
|
||||||
"""Mémoire la trajectoire suivie par l'étudiant"""
|
"""Mémoire la trajectoire suivie par l'étudiant"""
|
||||||
self.suivi[etudid][nom_aggregat] = trajectoire
|
self.suivi[etudid][nom_aggregat] = trajectoire
|
||||||
|
|
||||||
"""Vérifications"""
|
|
||||||
# dernier_semestre_aggregat = get_dernier_semestre_en_date(semestres_aggreges)
|
|
||||||
# assert dernier_semestre_aggregat == formsemestre_terminal
|
|
||||||
|
|
||||||
|
|
||||||
def get_trajectoires_etudid(trajectoires, etudid):
|
def get_trajectoires_etudid(trajectoires, etudid):
|
||||||
|
@ -142,26 +143,3 @@ def get_trajectoires_etudid(trajectoires, etudid):
|
||||||
return liste
|
return liste
|
||||||
|
|
||||||
|
|
||||||
def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int):
|
|
||||||
"""Pour un nom d'aggrégat donné (par ex: 'S3') et un semestre terminal cible
|
|
||||||
identifié par son formsemestre_id (par ex: 'S3 2022-2023'),
|
|
||||||
renvoie l'ensemble des semestres à prendre en compte dans
|
|
||||||
l'aggrégat sous la forme d'un dictionnaire {fid: FormSemestre(fid)}.
|
|
||||||
|
|
||||||
Fusionne les cursus individuels des étudiants, dont le cursus correspond
|
|
||||||
à l'aggrégat visé.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
aggregat: Un aggrégat (par ex. 1A, 2A, 3S, 6S)
|
|
||||||
formsemestre_id_terminal: L'identifiant du formsemestre terminal de l'aggrégat, devant correspondre au
|
|
||||||
dernier semestre de l'aggrégat
|
|
||||||
"""
|
|
||||||
noms_semestres_aggreges = pe_comp.PARCOURS[aggregat]["aggregat"]
|
|
||||||
|
|
||||||
formsemestres = {}
|
|
||||||
for etudid in self.cursus:
|
|
||||||
cursus_etudiant = self.cursus[etudid][aggregat]
|
|
||||||
if formsemestre_id_terminal in cursus_etudiant:
|
|
||||||
formsemestres_etudiant = cursus_etudiant[formsemestre_id_terminal]
|
|
||||||
formsemestres = formsemestres | formsemestres_etudiant
|
|
||||||
return formsemestres
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from app.pe.pe_trajectoire import Trajectoire
|
from app.pe.pe_trajectoire import Trajectoire
|
||||||
|
|
||||||
from app.pe.pe_tabletags import TableTag
|
from app.pe.pe_tabletags import TableTag, MoyenneTag
|
||||||
|
|
||||||
|
|
||||||
class TrajectoireTag(TableTag):
|
class TrajectoireTag(TableTag):
|
||||||
|
@ -58,6 +58,9 @@ class TrajectoireTag(TableTag):
|
||||||
|
|
||||||
Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S'
|
Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S'
|
||||||
|
|
||||||
|
Args:
|
||||||
|
trajectoire: Une trajectoire (aggrégat+semestre terminal)
|
||||||
|
semestres_taggues: Les données sur les semestres taggués
|
||||||
"""
|
"""
|
||||||
TableTag.__init__(self)
|
TableTag.__init__(self)
|
||||||
|
|
||||||
|
@ -68,8 +71,9 @@ class TrajectoireTag(TableTag):
|
||||||
# Le nom de la trajectoire tagguée (identique à la trajectoire)
|
# Le nom de la trajectoire tagguée (identique à la trajectoire)
|
||||||
self.nom = self.get_repr()
|
self.nom = self.get_repr()
|
||||||
|
|
||||||
self.formsemestre_terminal = trajectoire.semestre_final
|
self.formsemestre_terminal = trajectoire.formsemestre_final
|
||||||
"""Le formsemestre terminal"""
|
"""Le formsemestre terminal"""
|
||||||
|
|
||||||
# Les résultats du formsemestre terminal
|
# Les résultats du formsemestre terminal
|
||||||
nt = load_formsemestre_results(self.formsemestre_terminal)
|
nt = load_formsemestre_results(self.formsemestre_terminal)
|
||||||
|
|
||||||
|
@ -100,11 +104,11 @@ class TrajectoireTag(TableTag):
|
||||||
self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted)
|
self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted)
|
||||||
"""Calcul les moyennes par tag sous forme d'un dataframe"""
|
"""Calcul les moyennes par tag sous forme d'un dataframe"""
|
||||||
|
|
||||||
self.moyennes_tags = {}
|
self.moyennes_tags: dict[str, MoyenneTag] = {}
|
||||||
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
|
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
|
||||||
for tag in self.tags_sorted:
|
for tag in self.tags_sorted:
|
||||||
moy_gen_tag = self.notes[tag]
|
moy_gen_tag = self.notes[tag]
|
||||||
self.moyennes_tags[tag] = self.comp_moy_et_stat(moy_gen_tag)
|
self.moyennes_tags[tag] = MoyenneTag(tag, moy_gen_tag)
|
||||||
|
|
||||||
def get_repr(self, verbose=False) -> str:
|
def get_repr(self, verbose=False) -> str:
|
||||||
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
|
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
|
||||||
|
|
|
@ -39,6 +39,7 @@ from app import db, email
|
||||||
from app import log
|
from app import log
|
||||||
from app.models import Admission, Identite
|
from app.models import Admission, Identite
|
||||||
from app.models.etudiants import (
|
from app.models.etudiants import (
|
||||||
|
check_etud_duplicate_code,
|
||||||
input_civilite,
|
input_civilite,
|
||||||
input_civilite_etat_civil,
|
input_civilite_etat_civil,
|
||||||
make_etud_args,
|
make_etud_args,
|
||||||
|
@ -229,74 +230,12 @@ def check_nom_prenom_homonyms(
|
||||||
return True, query.all()
|
return True, query.all()
|
||||||
|
|
||||||
|
|
||||||
def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True):
|
|
||||||
"""Vérifie que le code n'est pas dupliqué.
|
|
||||||
Raises ScoGenError si problème.
|
|
||||||
"""
|
|
||||||
etudid = args.get("etudid", None)
|
|
||||||
if args.get(code_name, None):
|
|
||||||
etuds = identite_list(cnx, {code_name: str(args[code_name])})
|
|
||||||
duplicate = False
|
|
||||||
if edit:
|
|
||||||
duplicate = (len(etuds) > 1) or (
|
|
||||||
(len(etuds) == 1) and etuds[0]["id"] != args["etudid"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
duplicate = len(etuds) > 0
|
|
||||||
if duplicate:
|
|
||||||
listh = [] # liste des doubles
|
|
||||||
for e in etuds:
|
|
||||||
listh.append(
|
|
||||||
f"""Autre étudiant: <a href="{
|
|
||||||
url_for(
|
|
||||||
"scolar.fiche_etud",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
etudid=e["etudid"]
|
|
||||||
)}">{e['nom']} {e['prenom']}</a>"""
|
|
||||||
)
|
|
||||||
if etudid:
|
|
||||||
OK = "retour à la fiche étudiant"
|
|
||||||
dest_endpoint = "scolar.fiche_etud"
|
|
||||||
parameters = {"etudid": etudid}
|
|
||||||
else:
|
|
||||||
if "tf_submitted" in args:
|
|
||||||
del args["tf_submitted"]
|
|
||||||
OK = "Continuer"
|
|
||||||
dest_endpoint = "scolar.etudident_create_form"
|
|
||||||
parameters = args
|
|
||||||
else:
|
|
||||||
OK = "Annuler"
|
|
||||||
dest_endpoint = "notes.index_html"
|
|
||||||
parameters = {}
|
|
||||||
if not disable_notify:
|
|
||||||
err_page = f"""<h3><h3>Code étudiant ({code_name}) dupliqué !</h3>
|
|
||||||
<p class="help">Le {code_name} {args[code_name]} est déjà utilisé: un seul étudiant peut avoir
|
|
||||||
ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.
|
|
||||||
</p>
|
|
||||||
<ul><li>
|
|
||||||
{ '</li><li>'.join(listh) }
|
|
||||||
</li></ul>
|
|
||||||
<p>
|
|
||||||
<a href="{ url_for(dest_endpoint, scodoc_dept=g.scodoc_dept, **parameters) }
|
|
||||||
">{OK}</a>
|
|
||||||
</p>
|
|
||||||
"""
|
|
||||||
else:
|
|
||||||
err_page = f"""<h3>Code étudiant ({code_name}) dupliqué !</h3>"""
|
|
||||||
log(f"*** error: code {code_name} duplique: {args[code_name]}")
|
|
||||||
raise ScoGenError(err_page)
|
|
||||||
|
|
||||||
|
|
||||||
def identite_edit(cnx, args, disable_notify=False):
|
def identite_edit(cnx, args, disable_notify=False):
|
||||||
"""Modifie l'identite d'un étudiant.
|
"""Modifie l'identite d'un étudiant.
|
||||||
Si pref notification et difference, envoie message notification, sauf si disable_notify
|
Si pref notification et difference, envoie message notification, sauf si disable_notify
|
||||||
"""
|
"""
|
||||||
_check_duplicate_code(
|
check_etud_duplicate_code(args, "code_nip", edit=True)
|
||||||
cnx, args, "code_nip", disable_notify=disable_notify, edit=True
|
check_etud_duplicate_code(args, "code_ine", edit=True)
|
||||||
)
|
|
||||||
_check_duplicate_code(
|
|
||||||
cnx, args, "code_ine", disable_notify=disable_notify, edit=True
|
|
||||||
)
|
|
||||||
notify_to = None
|
notify_to = None
|
||||||
if not disable_notify:
|
if not disable_notify:
|
||||||
try:
|
try:
|
||||||
|
@ -325,16 +264,14 @@ def identite_edit(cnx, args, disable_notify=False):
|
||||||
|
|
||||||
def identite_create(cnx, args):
|
def identite_create(cnx, args):
|
||||||
"check unique etudid, then create"
|
"check unique etudid, then create"
|
||||||
_check_duplicate_code(cnx, args, "code_nip", edit=False)
|
check_etud_duplicate_code(args, "code_nip", edit=False)
|
||||||
_check_duplicate_code(cnx, args, "code_ine", edit=False)
|
check_etud_duplicate_code(args, "code_ine", edit=False)
|
||||||
|
|
||||||
if "etudid" in args:
|
if "etudid" in args:
|
||||||
etudid = args["etudid"]
|
etudid = args["etudid"]
|
||||||
r = identite_list(cnx, {"etudid": etudid})
|
r = identite_list(cnx, {"etudid": etudid})
|
||||||
if r:
|
if r:
|
||||||
raise ScoValueError(
|
raise ScoValueError(f"Code identifiant (etudid) déjà utilisé ! ({etudid})")
|
||||||
"Code identifiant (etudid) déjà utilisé ! (%s)" % etudid
|
|
||||||
)
|
|
||||||
return _identiteEditor.create(cnx, args)
|
return _identiteEditor.create(cnx, args)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ from app import db
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import Evaluation, FormSemestre
|
from app.models import Evaluation, FormSemestre, ModuleImpl
|
||||||
|
|
||||||
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
|
||||||
|
@ -280,82 +280,14 @@ def do_evaluation_etat(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
|
def _summarize_evals_etats(evals: list[dict]) -> dict:
|
||||||
"""Liste les évaluations de tous les modules de ce semestre.
|
"""Synthétise les états d'une liste d'évaluations
|
||||||
Triée par module, numero desc, date_debut desc
|
evals: list of mappings (etats),
|
||||||
Donne pour chaque eval son état (voir do_evaluation_etat)
|
utilise e["etat"]["evalcomplete"], e["etat"]["nb_notes"], e["etat"]["last_modif"]
|
||||||
{ evaluation_id,nb_inscrits, nb_notes, nb_abs, nb_neutre, moy, median, last_modif ... }
|
|
||||||
|
|
||||||
Exemple:
|
|
||||||
[ {
|
|
||||||
'coefficient': 1.0,
|
|
||||||
'description': 'QCM et cas pratiques',
|
|
||||||
'etat': {
|
|
||||||
'evalattente': False,
|
|
||||||
'evalcomplete': True,
|
|
||||||
'evaluation_id': 'GEAEVAL82883',
|
|
||||||
'gr_incomplets': [],
|
|
||||||
'gr_moyennes': [{
|
|
||||||
'gr_median': '12.00', # sur 20
|
|
||||||
'gr_moy': '11.88',
|
|
||||||
'gr_nb_att': 0,
|
|
||||||
'gr_nb_notes': 166,
|
|
||||||
'group_id': 'GEAG266762',
|
|
||||||
'group_name': None
|
|
||||||
}],
|
|
||||||
'groups': {'GEAG266762': {'etudid': 'GEAEID80603',
|
|
||||||
'group_id': 'GEAG266762',
|
|
||||||
'group_name': None,
|
|
||||||
'partition_id': 'GEAP266761'}
|
|
||||||
},
|
|
||||||
'last_modif': datetime.datetime(2015, 12, 3, 15, 15, 16),
|
|
||||||
'median': '12.00',
|
|
||||||
'moy': '11.84',
|
|
||||||
'nb_abs': 2,
|
|
||||||
'nb_att': 0,
|
|
||||||
'nb_inscrits': 166,
|
|
||||||
'nb_neutre': 0,
|
|
||||||
'nb_notes': 168,
|
|
||||||
'nb_notes_total': 169
|
|
||||||
},
|
|
||||||
'evaluation_id': 'GEAEVAL82883',
|
|
||||||
'evaluation_type': 0,
|
|
||||||
'heure_debut': datetime.time(8, 0),
|
|
||||||
'heure_fin': datetime.time(9, 30),
|
|
||||||
'jour': datetime.date(2015, 11, 3), // vide => 1/1/1900
|
|
||||||
'moduleimpl_id': 'GEAMIP80490',
|
|
||||||
'note_max': 20.0,
|
|
||||||
'numero': 0,
|
|
||||||
'publish_incomplete': 0,
|
|
||||||
'visibulletin': 1} ]
|
|
||||||
|
|
||||||
"""
|
|
||||||
req = """SELECT E.id AS evaluation_id, E.*
|
|
||||||
FROM notes_evaluation E, notes_moduleimpl MI
|
|
||||||
WHERE MI.formsemestre_id = %(formsemestre_id)s
|
|
||||||
and MI.id = E.moduleimpl_id
|
|
||||||
ORDER BY MI.id, numero desc, date_debut desc
|
|
||||||
"""
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
||||||
res = cursor.dictfetchall()
|
|
||||||
# etat de chaque evaluation:
|
|
||||||
for r in res:
|
|
||||||
if with_etat:
|
|
||||||
r["etat"] = do_evaluation_etat(r["evaluation_id"])
|
|
||||||
r["jour"] = r["date_debut"] or datetime.date(1900, 1, 1)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def _eval_etat(evals):
|
|
||||||
"""evals: list of mappings (etats)
|
|
||||||
-> nb_eval_completes, nb_evals_en_cours,
|
-> nb_eval_completes, nb_evals_en_cours,
|
||||||
nb_evals_vides, date derniere modif
|
nb_evals_vides, date derniere modif
|
||||||
|
|
||||||
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
|
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
nb_evals_completes, nb_evals_en_cours, nb_evals_vides = 0, 0, 0
|
nb_evals_completes, nb_evals_en_cours, nb_evals_vides = 0, 0, 0
|
||||||
dates = []
|
dates = []
|
||||||
|
@ -370,11 +302,8 @@ def _eval_etat(evals):
|
||||||
if last_modif is not None:
|
if last_modif is not None:
|
||||||
dates.append(e["etat"]["last_modif"])
|
dates.append(e["etat"]["last_modif"])
|
||||||
|
|
||||||
if dates:
|
# date de derniere modif d'une note dans un module
|
||||||
dates = scu.sort_dates(dates)
|
last_modif = sorted(dates)[-1] if dates else ""
|
||||||
last_modif = dates[-1] # date de derniere modif d'une note dans un module
|
|
||||||
else:
|
|
||||||
last_modif = ""
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"nb_evals_completes": nb_evals_completes,
|
"nb_evals_completes": nb_evals_completes,
|
||||||
|
@ -384,37 +313,42 @@ def _eval_etat(evals):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_etat_in_sem(formsemestre_id):
|
def do_evaluation_etat_in_sem(formsemestre: FormSemestre) -> dict:
|
||||||
"""-> nb_eval_completes, nb_evals_en_cours, nb_evals_vides,
|
"""-> { nb_eval_completes, nb_evals_en_cours, nb_evals_vides,
|
||||||
date derniere modif, attente
|
date derniere modif, attente }
|
||||||
|
|
||||||
XXX utilisé par
|
|
||||||
- formsemestre_status_head
|
|
||||||
- gen_formsemestre_recapcomplet_xml
|
|
||||||
- gen_formsemestre_recapcomplet_json
|
|
||||||
|
|
||||||
"nb_evals_completes"
|
|
||||||
"nb_evals_en_cours"
|
|
||||||
"nb_evals_vides"
|
|
||||||
"date_derniere_note"
|
|
||||||
"last_modif"
|
|
||||||
"attente"
|
|
||||||
"""
|
"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
# Note: utilisé par
|
||||||
|
# - formsemestre_status_head
|
||||||
|
# nb_evals_completes, nb_evals_en_cours, nb_evals_vides, last_modif
|
||||||
|
# pour la ligne
|
||||||
|
# Évaluations: 20 ok, 8 en cours, 5 vides (dernière note saisie le 11/01/2024 à 19h49)
|
||||||
|
# attente
|
||||||
|
#
|
||||||
|
# - gen_formsemestre_recapcomplet_xml
|
||||||
|
# - gen_formsemestre_recapcomplet_json
|
||||||
|
# nb_evals_completes, nb_evals_en_cours, nb_evals_vides, last_modif
|
||||||
|
#
|
||||||
|
# "nb_evals_completes"
|
||||||
|
# "nb_evals_en_cours"
|
||||||
|
# "nb_evals_vides"
|
||||||
|
# "last_modif"
|
||||||
|
# "attente"
|
||||||
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
evals = nt.get_evaluations_etats()
|
evaluations_etats = nt.get_evaluations_etats()
|
||||||
etat = _eval_etat(evals)
|
# raccordement moche...
|
||||||
|
etat = _summarize_evals_etats([{"etat": v} for v in evaluations_etats.values()])
|
||||||
# Ajoute information sur notes en attente
|
# Ajoute information sur notes en attente
|
||||||
etat["attente"] = len(nt.get_moduleimpls_attente()) > 0
|
etat["attente"] = len(nt.get_moduleimpls_attente()) > 0
|
||||||
return etat
|
return etat
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_etat_in_mod(nt, moduleimpl_id):
|
def do_evaluation_etat_in_mod(nt, modimpl: ModuleImpl):
|
||||||
"""état des évaluations dans ce module"""
|
"""état des évaluations dans ce module"""
|
||||||
evals = nt.get_mod_evaluation_etat_list(moduleimpl_id)
|
evals = nt.get_mod_evaluation_etat_list(modimpl)
|
||||||
etat = _eval_etat(evals)
|
etat = _summarize_evals_etats(evals)
|
||||||
# Il y a-t-il des notes en attente dans ce module ?
|
# Il y a-t-il des notes en attente dans ce module ?
|
||||||
etat["attente"] = nt.modimpls_results[moduleimpl_id].en_attente
|
etat["attente"] = nt.modimpls_results[modimpl.id].en_attente
|
||||||
return etat
|
return etat
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -230,3 +230,15 @@ class APIInvalidParams(Exception):
|
||||||
|
|
||||||
class ScoFormationConflict(Exception):
|
class ScoFormationConflict(Exception):
|
||||||
"""Conflit cohérence formation (APC)"""
|
"""Conflit cohérence formation (APC)"""
|
||||||
|
|
||||||
|
|
||||||
|
class ScoTemporaryError(ScoValueError):
|
||||||
|
"""Erreurs temporaires rarissimes (caches ?)"""
|
||||||
|
|
||||||
|
def __init__(self, msg: str = ""):
|
||||||
|
msg = """
|
||||||
|
<p>"Erreur temporaire</p>
|
||||||
|
<p>Veuillez ré-essayer. Si le problème persiste, merci de contacter l'assistance ScoDoc
|
||||||
|
</p>
|
||||||
|
"""
|
||||||
|
super().__init__(msg)
|
||||||
|
|
|
@ -627,9 +627,7 @@ def formsemestre_description_table(
|
||||||
# car l'UE de rattachement n'a pas d'intérêt en BUT
|
# car l'UE de rattachement n'a pas d'intérêt en BUT
|
||||||
rows.append(ue_info)
|
rows.append(ue_info)
|
||||||
|
|
||||||
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
|
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
|
||||||
moduleimpl_id=modimpl.id
|
|
||||||
)
|
|
||||||
enseignants = ", ".join(ens.get_prenomnom() for ens in modimpl.enseignants)
|
enseignants = ", ".join(ens.get_prenomnom() for ens in modimpl.enseignants)
|
||||||
|
|
||||||
row = {
|
row = {
|
||||||
|
@ -638,7 +636,7 @@ def formsemestre_description_table(
|
||||||
"Code": modimpl.module.code or "",
|
"Code": modimpl.module.code or "",
|
||||||
"Module": modimpl.module.abbrev or modimpl.module.titre,
|
"Module": modimpl.module.abbrev or modimpl.module.titre,
|
||||||
"_Module_class": "scotext",
|
"_Module_class": "scotext",
|
||||||
"Inscrits": len(mod_inscrits),
|
"Inscrits": mod_nb_inscrits,
|
||||||
"Responsable": sco_users.user_info(modimpl.responsable_id)["nomprenom"],
|
"Responsable": sco_users.user_info(modimpl.responsable_id)["nomprenom"],
|
||||||
"_Responsable_class": "scotext",
|
"_Responsable_class": "scotext",
|
||||||
"Enseignants": enseignants,
|
"Enseignants": enseignants,
|
||||||
|
@ -680,7 +678,7 @@ def formsemestre_description_table(
|
||||||
|
|
||||||
if with_evals:
|
if with_evals:
|
||||||
# Ajoute lignes pour evaluations
|
# Ajoute lignes pour evaluations
|
||||||
evals = nt.get_mod_evaluation_etat_list(modimpl.id)
|
evals = nt.get_mod_evaluation_etat_list(modimpl)
|
||||||
evals.reverse() # ordre chronologique
|
evals.reverse() # ordre chronologique
|
||||||
# Ajoute etat:
|
# Ajoute etat:
|
||||||
eval_rows = []
|
eval_rows = []
|
||||||
|
@ -942,10 +940,10 @@ def html_expr_diagnostic(diagnostics):
|
||||||
|
|
||||||
def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None):
|
def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None):
|
||||||
"""En-tête HTML des pages "semestre" """
|
"""En-tête HTML des pages "semestre" """
|
||||||
sem: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||||
if not sem:
|
if not formsemestre:
|
||||||
raise ScoValueError("Semestre inexistant (il a peut être été supprimé ?)")
|
raise ScoValueError("Semestre inexistant (il a peut être été supprimé ?)")
|
||||||
formation: Formation = sem.formation
|
formation: Formation = formsemestre.formation
|
||||||
parcours = formation.get_cursus()
|
parcours = formation.get_cursus()
|
||||||
|
|
||||||
page_title = page_title or "Modules de "
|
page_title = page_title or "Modules de "
|
||||||
|
@ -957,25 +955,25 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
|
||||||
f"""<table>
|
f"""<table>
|
||||||
<tr><td class="fichetitre2">Formation: </td><td>
|
<tr><td class="fichetitre2">Formation: </td><td>
|
||||||
<a href="{url_for('notes.ue_table',
|
<a href="{url_for('notes.ue_table',
|
||||||
scodoc_dept=g.scodoc_dept, formation_id=sem.formation.id)}"
|
scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)}"
|
||||||
class="discretelink" title="Formation {
|
class="discretelink" title="Formation {
|
||||||
formation.acronyme}, v{formation.version}">{formation.titre}</a>
|
formation.acronyme}, v{formation.version}">{formation.titre}</a>
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
if sem.semestre_id >= 0:
|
if formsemestre.semestre_id >= 0:
|
||||||
H.append(f", {parcours.SESSION_NAME} {sem.semestre_id}")
|
H.append(f", {parcours.SESSION_NAME} {formsemestre.semestre_id}")
|
||||||
if sem.modalite:
|
if formsemestre.modalite:
|
||||||
H.append(f" en {sem.modalite}")
|
H.append(f" en {formsemestre.modalite}")
|
||||||
if sem.etapes:
|
if formsemestre.etapes:
|
||||||
H.append(
|
H.append(
|
||||||
f""" (étape <b><tt>{
|
f""" (étape <b><tt>{
|
||||||
sem.etapes_apo_str() or "-"
|
formsemestre.etapes_apo_str() or "-"
|
||||||
}</tt></b>)"""
|
}</tt></b>)"""
|
||||||
)
|
)
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
if formation.is_apc():
|
if formation.is_apc():
|
||||||
# Affiche les parcours BUT cochés. Si aucun, tous ceux du référentiel.
|
# Affiche les parcours BUT cochés. Si aucun, tous ceux du référentiel.
|
||||||
sem_parcours = sem.get_parcours_apc()
|
sem_parcours = formsemestre.get_parcours_apc()
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<tr><td class="fichetitre2">Parcours: </td>
|
<tr><td class="fichetitre2">Parcours: </td>
|
||||||
|
@ -984,7 +982,7 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
|
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
|
||||||
H.append(
|
H.append(
|
||||||
'<tr><td class="fichetitre2">Évaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides'
|
'<tr><td class="fichetitre2">Évaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides'
|
||||||
% evals
|
% evals
|
||||||
|
@ -1002,11 +1000,11 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
|
||||||
"""<span class="fontred">Il y a des notes en attente !</span>
|
"""<span class="fontred">Il y a des notes en attente !</span>
|
||||||
Le classement des étudiants n'a qu'une valeur indicative."""
|
Le classement des étudiants n'a qu'une valeur indicative."""
|
||||||
)
|
)
|
||||||
if sem.bul_hide_xml:
|
if formsemestre.bul_hide_xml:
|
||||||
warnings.append("""Bulletins non publiés sur la passerelle.""")
|
warnings.append("""Bulletins non publiés sur la passerelle.""")
|
||||||
if sem.block_moyennes:
|
if formsemestre.block_moyennes:
|
||||||
warnings.append("Calcul des moyennes bloqué !")
|
warnings.append("Calcul des moyennes bloqué !")
|
||||||
if sem.semestre_id >= 0 and not sem.est_sur_une_annee():
|
if formsemestre.semestre_id >= 0 and not formsemestre.est_sur_une_annee():
|
||||||
warnings.append("""<em>Ce semestre couvre plusieurs années scolaires !</em>""")
|
warnings.append("""<em>Ce semestre couvre plusieurs années scolaires !</em>""")
|
||||||
if warnings:
|
if warnings:
|
||||||
H += [
|
H += [
|
||||||
|
@ -1028,18 +1026,14 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||||
# S'assure que les groupes de parcours sont à jour:
|
# S'assure que les groupes de parcours sont à jour:
|
||||||
if int(check_parcours):
|
if int(check_parcours):
|
||||||
formsemestre.setup_parcours_groups()
|
formsemestre.setup_parcours_groups()
|
||||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
modimpls = formsemestre.modimpls_sorted
|
||||||
formsemestre_id=formsemestre_id
|
|
||||||
)
|
|
||||||
nt = res_sem.load_formsemestre_results(formsemestre)
|
nt = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
# Construit la liste de tous les enseignants de ce semestre:
|
# Construit la liste de tous les enseignants de ce semestre:
|
||||||
mails_enseignants = set(u.email for u in formsemestre.responsables)
|
mails_enseignants = set(u.email for u in formsemestre.responsables)
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
mails_enseignants.add(sco_users.user_info(modimpl["responsable_id"])["email"])
|
mails_enseignants.add(sco_users.user_info(modimpl.responsable_id)["email"])
|
||||||
mails_enseignants |= set(
|
mails_enseignants |= {u.email for u in modimpl.enseignants if u.email}
|
||||||
[sco_users.user_info(m["ens_id"])["email"] for m in modimpl["ens"]]
|
|
||||||
)
|
|
||||||
|
|
||||||
can_edit = formsemestre.can_be_edited_by(current_user)
|
can_edit = formsemestre.can_be_edited_by(current_user)
|
||||||
can_change_all_notes = current_user.has_permission(Permission.EditAllNotes) or (
|
can_change_all_notes = current_user.has_permission(Permission.EditAllNotes) or (
|
||||||
|
@ -1089,13 +1083,13 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||||
if nt.parcours.APC_SAE:
|
if nt.parcours.APC_SAE:
|
||||||
# BUT: tableau ressources puis SAE
|
# BUT: tableau ressources puis SAE
|
||||||
ressources = [
|
ressources = [
|
||||||
m for m in modimpls if m["module"]["module_type"] == ModuleType.RESSOURCE
|
m for m in modimpls if m.module.module_type == ModuleType.RESSOURCE
|
||||||
]
|
]
|
||||||
saes = [m for m in modimpls if m["module"]["module_type"] == ModuleType.SAE]
|
saes = [m for m in modimpls if m.module.module_type == ModuleType.SAE]
|
||||||
autres = [
|
autres = [
|
||||||
m
|
m
|
||||||
for m in modimpls
|
for m in modimpls
|
||||||
if m["module"]["module_type"] not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
if m.module.module_type not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
||||||
]
|
]
|
||||||
H += [
|
H += [
|
||||||
f"""
|
f"""
|
||||||
|
@ -1136,7 +1130,7 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||||
modimpls_classic = [
|
modimpls_classic = [
|
||||||
m
|
m
|
||||||
for m in modimpls
|
for m in modimpls
|
||||||
if m["module"]["module_type"] not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
if m.module.module_type not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
||||||
]
|
]
|
||||||
H += [
|
H += [
|
||||||
"<p>",
|
"<p>",
|
||||||
|
@ -1168,8 +1162,10 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||||
adrlist = list(mails_enseignants - {None, ""})
|
adrlist = list(mails_enseignants - {None, ""})
|
||||||
if adrlist:
|
if adrlist:
|
||||||
H.append(
|
H.append(
|
||||||
'<p><a class="stdlink" href="mailto:?cc=%s">Courrier aux %d enseignants du semestre</a></p>'
|
f"""<p>
|
||||||
% (",".join(adrlist), len(adrlist))
|
<a class="stdlink" href="mailto:?cc={','.join(adrlist)}">Courrier aux {
|
||||||
|
len(adrlist)} enseignants du semestre</a>
|
||||||
|
</p>"""
|
||||||
)
|
)
|
||||||
return "".join(H) + html_sco_header.sco_footer()
|
return "".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
@ -1189,7 +1185,7 @@ _TABLEAU_MODULES_FOOT = """</table>"""
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_tableau_modules(
|
def formsemestre_tableau_modules(
|
||||||
modimpls: list[dict],
|
modimpls: list[ModuleImpl],
|
||||||
nt,
|
nt,
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
can_edit=True,
|
can_edit=True,
|
||||||
|
@ -1200,11 +1196,11 @@ def formsemestre_tableau_modules(
|
||||||
H = []
|
H = []
|
||||||
prev_ue_id = None
|
prev_ue_id = None
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
mod: Module = db.session.get(Module, modimpl["module_id"])
|
mod: Module = modimpl.module
|
||||||
moduleimpl_status_url = url_for(
|
moduleimpl_status_url = url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
moduleimpl_id=modimpl["moduleimpl_id"],
|
moduleimpl_id=modimpl.id,
|
||||||
)
|
)
|
||||||
mod_descr = "Module " + (mod.titre or "")
|
mod_descr = "Module " + (mod.titre or "")
|
||||||
if mod.is_apc():
|
if mod.is_apc():
|
||||||
|
@ -1221,48 +1217,45 @@ def formsemestre_tableau_modules(
|
||||||
mod_descr += " (pas de coefficients) "
|
mod_descr += " (pas de coefficients) "
|
||||||
else:
|
else:
|
||||||
mod_descr += ", coef. " + str(mod.coefficient)
|
mod_descr += ", coef. " + str(mod.coefficient)
|
||||||
mod_ens = sco_users.user_info(modimpl["responsable_id"])["nomcomplet"]
|
mod_ens = sco_users.user_info(modimpl.responsable_id)["nomcomplet"]
|
||||||
if modimpl["ens"]:
|
if modimpl.enseignants.count():
|
||||||
mod_ens += " (resp.), " + ", ".join(
|
mod_ens += " (resp.), " + ", ".join(
|
||||||
[sco_users.user_info(e["ens_id"])["nomcomplet"] for e in modimpl["ens"]]
|
[u.get_nomcomplet() for u in modimpl.enseignants]
|
||||||
)
|
)
|
||||||
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
|
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
|
||||||
moduleimpl_id=modimpl["moduleimpl_id"]
|
ue = modimpl.module.ue
|
||||||
)
|
if show_ues and (prev_ue_id != ue.id):
|
||||||
|
prev_ue_id = ue.id
|
||||||
ue = modimpl["ue"]
|
titre = ue.titre
|
||||||
if show_ues and (prev_ue_id != ue["ue_id"]):
|
|
||||||
prev_ue_id = ue["ue_id"]
|
|
||||||
titre = ue["titre"]
|
|
||||||
if use_ue_coefs:
|
if use_ue_coefs:
|
||||||
titre += f""" <b>(coef. {ue["coefficient"] or 0.0})</b>"""
|
titre += f""" <b>(coef. {ue.coefficient or 0.0})</b>"""
|
||||||
H.append(
|
H.append(
|
||||||
f"""<tr class="formsemestre_status_ue"><td colspan="4">
|
f"""<tr class="formsemestre_status_ue"><td colspan="4">
|
||||||
<span class="status_ue_acro">{ue["acronyme"]}</span>
|
<span class="status_ue_acro">{ue.acronyme}</span>
|
||||||
<span class="status_ue_title">{titre}</span>
|
<span class="status_ue_title">{titre}</span>
|
||||||
</td><td colspan="2">"""
|
</td><td colspan="2">"""
|
||||||
)
|
)
|
||||||
|
|
||||||
expr = sco_compute_moy.get_ue_expression(
|
expr = sco_compute_moy.get_ue_expression(
|
||||||
formsemestre.id, ue["ue_id"], html_quote=True
|
formsemestre.id, ue.id, html_quote=True
|
||||||
)
|
)
|
||||||
if expr:
|
if expr:
|
||||||
H.append(
|
H.append(
|
||||||
f""" <span class="formula" title="mode de calcul de la moyenne d'UE">{expr}</span>
|
f""" <span class="formula" title="mode de calcul de la moyenne d'UE">{expr}</span>
|
||||||
<span class="warning">formule inutilisée en 9.2: <a href="{
|
<span class="warning">formule inutilisée en 9.2: <a href="{
|
||||||
url_for("notes.delete_ue_expr", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, ue_id=ue["ue_id"] )
|
url_for("notes.delete_ue_expr", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, ue_id=ue.id )
|
||||||
}
|
}
|
||||||
">supprimer</a></span>"""
|
">supprimer</a></span>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
|
|
||||||
if modimpl["ue"]["type"] != codes_cursus.UE_STANDARD:
|
if ue.type != codes_cursus.UE_STANDARD:
|
||||||
fontorange = " fontorange" # style css additionnel
|
fontorange = " fontorange" # style css additionnel
|
||||||
else:
|
else:
|
||||||
fontorange = ""
|
fontorange = ""
|
||||||
|
|
||||||
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl["moduleimpl_id"])
|
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl)
|
||||||
# if nt.parcours.APC_SAE:
|
# if nt.parcours.APC_SAE:
|
||||||
# tbd style si module non conforme
|
# tbd style si module non conforme
|
||||||
if (
|
if (
|
||||||
|
@ -1282,10 +1275,10 @@ def formsemestre_tableau_modules(
|
||||||
<td class="scotext"><a href="{moduleimpl_status_url}" title="{mod_descr}"
|
<td class="scotext"><a href="{moduleimpl_status_url}" title="{mod_descr}"
|
||||||
class="formsemestre_status_link">{mod.abbrev or mod.titre or ""}</a>
|
class="formsemestre_status_link">{mod.abbrev or mod.titre or ""}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="formsemestre_status_inscrits">{len(mod_inscrits)}</td>
|
<td class="formsemestre_status_inscrits">{mod_nb_inscrits}</td>
|
||||||
<td class="resp scotext">
|
<td class="resp scotext">
|
||||||
<a class="discretelink" href="{moduleimpl_status_url}" title="{mod_ens}">{
|
<a class="discretelink" href="{moduleimpl_status_url}" title="{mod_ens}">{
|
||||||
sco_users.user_info(modimpl["responsable_id"])["prenomnom"]
|
sco_users.user_info(modimpl.responsable_id)["prenomnom"]
|
||||||
}</a>
|
}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1339,10 +1332,7 @@ def formsemestre_tableau_modules(
|
||||||
)
|
)
|
||||||
elif mod.module_type == ModuleType.MALUS:
|
elif mod.module_type == ModuleType.MALUS:
|
||||||
nb_malus_notes = sum(
|
nb_malus_notes = sum(
|
||||||
[
|
e["etat"]["nb_notes"] for e in nt.get_mod_evaluation_etat_list(modimpl)
|
||||||
e["etat"]["nb_notes"]
|
|
||||||
for e in nt.get_mod_evaluation_etat_list(modimpl["moduleimpl_id"])
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
f"""<td class="malus">
|
f"""<td class="malus">
|
||||||
|
|
|
@ -508,15 +508,20 @@ def students_import_admission(
|
||||||
H = [html_sco_header.sco_header(page_title="Import données admissions")]
|
H = [html_sco_header.sco_header(page_title="Import données admissions")]
|
||||||
H.append("<p>Import terminé !</p>")
|
H.append("<p>Import terminé !</p>")
|
||||||
H.append(
|
H.append(
|
||||||
'<p><a class="stdlink" href="%s">Continuer</a></p>'
|
f"""<p><a class="stdlink" href="{ url_for(
|
||||||
% url_for(
|
|
||||||
"notes.formsemestre_status",
|
"notes.formsemestre_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
)
|
)
|
||||||
|
}">Continuer</a></p>"""
|
||||||
)
|
)
|
||||||
if diag:
|
if diag:
|
||||||
H.append("<p>Diagnostic: <ul><li>%s</li></ul></p>" % "</li><li>".join(diag))
|
H.append(
|
||||||
|
f"""<p>Diagnostic: <ul><li>{
|
||||||
|
"</li><li>".join(diag)
|
||||||
|
}</li></ul></p>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
|
@ -367,7 +367,7 @@ def gen_formsemestre_recapcomplet_xml(
|
||||||
doc = ElementTree.Element(
|
doc = ElementTree.Element(
|
||||||
"recapsemestre", formsemestre_id=str(formsemestre_id), date=docdate
|
"recapsemestre", formsemestre_id=str(formsemestre_id), date=docdate
|
||||||
)
|
)
|
||||||
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
|
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
|
||||||
doc.append(
|
doc.append(
|
||||||
ElementTree.Element(
|
ElementTree.Element(
|
||||||
"evals_info",
|
"evals_info",
|
||||||
|
@ -408,7 +408,7 @@ def gen_formsemestre_recapcomplet_json(
|
||||||
docdate = ""
|
docdate = ""
|
||||||
else:
|
else:
|
||||||
docdate = datetime.datetime.now().isoformat()
|
docdate = datetime.datetime.now().isoformat()
|
||||||
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
|
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
|
||||||
js_data = {
|
js_data = {
|
||||||
"docdate": docdate,
|
"docdate": docdate,
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
|
|
|
@ -1440,17 +1440,6 @@ EMO_PREV_ARROW = "❮"
|
||||||
EMO_NEXT_ARROW = "❯"
|
EMO_NEXT_ARROW = "❯"
|
||||||
|
|
||||||
|
|
||||||
def sort_dates(L, reverse=False):
|
|
||||||
"""Return sorted list of dates, allowing None items (they are put at the beginning)"""
|
|
||||||
mindate = datetime.datetime(datetime.MINYEAR, 1, 1)
|
|
||||||
try:
|
|
||||||
return sorted(L, key=lambda x: x or mindate, reverse=reverse)
|
|
||||||
except:
|
|
||||||
# Helps debugging
|
|
||||||
log("sort_dates( %s )" % L)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def heterogeneous_sorting_key(x):
|
def heterogeneous_sorting_key(x):
|
||||||
"key to sort non homogeneous sequences"
|
"key to sort non homogeneous sequences"
|
||||||
return (float(x), "") if isinstance(x, (bool, float, int)) else (-1e34, str(x))
|
return (float(x), "") if isinstance(x, (bool, float, int)) else (-1e34, str(x))
|
||||||
|
|
|
@ -3106,7 +3106,10 @@ def formsemestre_set_elt_sem_apo():
|
||||||
"""Change les codes étapes du semestre indiqué.
|
"""Change les codes étapes du semestre indiqué.
|
||||||
Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
||||||
"""
|
"""
|
||||||
oid = int(request.form.get("oid"))
|
try:
|
||||||
|
oid = int(request.form.get("oid"))
|
||||||
|
except (TypeError, ValueError) as exc:
|
||||||
|
raise ScoValueError("paramètre invalide") from exc
|
||||||
value = (request.form.get("value") or "").strip()
|
value = (request.form.get("value") or "").strip()
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid)
|
||||||
if value != formsemestre.elt_sem_apo:
|
if value != formsemestre.elt_sem_apo:
|
||||||
|
|
|
@ -2306,23 +2306,31 @@ def form_students_import_infos_admissions(formsemestre_id=None):
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title="Export données admissions (Parcoursup ou autre)",
|
page_title="Export données admissions (Parcoursup ou autre)",
|
||||||
),
|
),
|
||||||
"""<h2 class="formsemestre">Téléchargement des informations sur l'admission des étudiants</h2>
|
f"""<h2 class="formsemestre">Téléchargement des informations sur l'admission
|
||||||
|
des étudiants</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="import_generate_admission_sample?formsemestre_id=%(formsemestre_id)s">Exporter les informations de ScoDoc (classeur Excel)</a> (ce fichier peut être ré-importé après d'éventuelles modifications)
|
<a href="{ url_for('scolar.import_generate_admission_sample',
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id )
|
||||||
|
}">Exporter les informations de ScoDoc (classeur Excel)</a> (ce fichier
|
||||||
|
peut être ré-importé après d'éventuelles modifications)
|
||||||
</p>
|
</p>
|
||||||
<p class="warning">Vous n'avez pas le droit d'importer les données</p>
|
<p class="warning">Vous n'avez pas le droit d'importer les données</p>
|
||||||
"""
|
""",
|
||||||
% {"formsemestre_id": formsemestre_id},
|
|
||||||
]
|
]
|
||||||
return "\n".join(H) + F
|
return "\n".join(H) + F
|
||||||
|
|
||||||
# On a le droit d'importer:
|
# On a le droit d'importer:
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Import données admissions Parcoursup"),
|
html_sco_header.sco_header(page_title="Import données admissions Parcoursup"),
|
||||||
f"""<h2 class="formsemestre">Téléchargement des informations sur l'admission des étudiants depuis feuilles import Parcoursup</h2>
|
f"""<h2 class="formsemestre">Téléchargement des informations sur l'admission des étudiants
|
||||||
<div style="color: red">
|
depuis feuilles import Parcoursup</h2>
|
||||||
<p>A utiliser pour renseigner les informations sur l'origine des étudiants (lycées, bac, etc). Ces informations sont facultatives mais souvent utiles pour mieux connaitre les étudiants et aussi pour effectuer des statistiques (résultats suivant le type de bac...). Les données sont affichées sur les fiches individuelles des étudiants.</p>
|
<div style="color: red">
|
||||||
</div>
|
<p>A utiliser pour renseigner les informations sur l'origine des étudiants (lycées, bac, etc).
|
||||||
|
Ces informations sont facultatives mais souvent utiles pour mieux connaitre les étudiants
|
||||||
|
et aussi pour effectuer des statistiques (résultats suivant le type de bac...).
|
||||||
|
Les données sont affichées sur les fiches individuelles des étudiants.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<p>
|
<p>
|
||||||
Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup.
|
Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup.
|
||||||
Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
|
Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.90"
|
SCOVERSION = "9.6.92"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import json
|
||||||
import requests
|
import requests
|
||||||
from types import NoneType
|
from types import NoneType
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
from tests.api.setup_test_api import (
|
from tests.api.setup_test_api import (
|
||||||
|
@ -617,7 +619,8 @@ def test_formsemestre_programme(api_headers):
|
||||||
assert verify_fields(sae, MODIMPL_FIELDS)
|
assert verify_fields(sae, MODIMPL_FIELDS)
|
||||||
|
|
||||||
|
|
||||||
def test_etat_evals(api_headers):
|
@pytest.mark.skip # XXX WIP
|
||||||
|
def test_etat_evals(api_headers): # voir si on maintient cette route ?
|
||||||
"""
|
"""
|
||||||
Route : /formsemestre/<int:formsemestre_id>/etat_evals
|
Route : /formsemestre/<int:formsemestre_id>/etat_evals
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue
Block a user