Compare commits

...

6 Commits

7 changed files with 198 additions and 24 deletions

View File

@ -50,7 +50,7 @@ from app.api.errors import error_response
from app import models from app import models
from app.models import FormSemestre, FormSemestreInscription, Identite from app.models import FormSemestre, FormSemestreInscription, Identite
from app.models import ApcReferentielCompetences from app.models import ApcReferentielCompetences
from app.scodoc.sco_abs import annule_absence, annule_justif from app.scodoc.sco_abs import annule_absence, annule_justif, add_absence, add_justif
from app.scodoc.sco_bulletins import formsemestre_bulletinetud_dict from app.scodoc.sco_bulletins import formsemestre_bulletinetud_dict
from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
@ -343,17 +343,27 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
""" """
La formation, avec UE, matières, modules La formation, avec UE, matières, modules
""" """
data = formation_export(formation_id) try:
data = formation_export(formation_id)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
return jsonify(data) return jsonify(data)
@bp.route("/formations/apo/<int:etape_apo>", methods=["GET"]) @bp.route("/formations/apo/<string:etape_apo>", methods=["GET"])
def formsemestre_apo(etape_apo: int): def formsemestre_apo(etape_apo: int):
""" """
Information sur les formsemestres Information sur les formsemestres
""" """
return error_response(501, message="Not implemented") apos = models.FormSemestreEtape.query.filter_by(etape_apo=etape_apo).all()
data = []
for apo in apos:
formsem = models.FormSemestre.query.filter_by(id=apo["formsemestre_id"]).first()
data.append(formsem.to_dict())
return jsonify(data)
# return error_response(501, message="Not implemented")
@bp.route("/formations/moduleimpl/<int:moduleimpl_id>", methods=["GET"]) @bp.route("/formations/moduleimpl/<int:moduleimpl_id>", methods=["GET"])
@ -376,9 +386,12 @@ def moduleimpls_sem(moduleimpl_id: int, formsemestre_id: int):
""" """
Liste de moduleimpl d'un semestre Liste de moduleimpl d'un semestre
""" """
data = moduleimpl_list(moduleimpl_id, formsemestre_id) try:
data = moduleimpl_list(moduleimpl_id, formsemestre_id)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
return jsonify(data) return jsonify(data)
# return error_response(501, message="Not implemented")
#################################################### UE ############################################################### #################################################### UE ###############################################################
@ -433,7 +446,7 @@ def etudiant_bulletin(formsemestre_id, dept, etudid, format="json", *args, size)
elif args[0] == "long": elif args[0] == "long":
data = formsemestre_bulletinetud_dict(formsemestre_id, etudid) data = formsemestre_bulletinetud_dict(formsemestre_id, etudid)
else: else:
return error_response(501, message="Not implemented") return error_response(409, message="La requête ne peut être traitée en létat actuel")
return jsonify(data) return jsonify(data)
@ -623,7 +636,9 @@ def annule_decision_jury(formsemestre_id: int, etudid=None, nip=None, ine=None):
#################################################### Absences ######################################################### #################################################### Absences #########################################################
@bp.route("/absences/<int:etudid>", methods=["GET"]) @bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
@bp.route("/absences/nip/<int:nip>", methods=["GET"])
@bp.route("/absences/ine/<int:ine>", methods=["GET"])
def absences(etudid=None, nip=None, ine=None): def absences(etudid=None, nip=None, ine=None):
""" """
Liste des absences d'un étudiant donné Liste des absences d'un étudiant donné
@ -646,7 +661,9 @@ def absences(etudid=None, nip=None, ine=None):
return error_response(501, message="Not implemented") return error_response(501, message="Not implemented")
@bp.route("/absences/<int:etudid>/abs_just_only", methods=["GET"]) @bp.route("/absences/etudid/<int:etudid>/abs_just_only", methods=["GET"])
@bp.route("/absences/nip/<int:nip>/abs_just_only", methods=["GET"])
@bp.route("/absences/ine/<int:ine>/abs_just_only", methods=["GET"])
def absences_justify(etudid=None, nip=None, ine=None): def absences_justify(etudid=None, nip=None, ine=None):
""" """
Liste des absences justifiés d'un étudiant donné Liste des absences justifiés d'un étudiant donné
@ -669,13 +686,89 @@ def absences_justify(etudid=None, nip=None, ine=None):
return error_response(501, message="Not implemented") return error_response(501, message="Not implemented")
@bp.route("/absences/abs_signale", methods=["POST"]) @bp.route("/absences/abs_signale?etudid=<int:etudid>&date=<string:date>&matin=<string:matin>&justif=<string:justif>",
"&description=<string:description>", methods=["POST"])
@bp.route("/absences/abs_signale?nip=<int:nip>&date=<string:date>&matin=<string:matin>&justif=<string:justif>",
"&description=<string:description>", methods=["POST"])
@bp.route("/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>", methods=["POST"])
@bp.route("/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
"&description=<string:description>&moduleimpl_id=<int:moduleimpl_id>", methods=["POST"])
@token_auth.login_required @token_auth.login_required
def abs_signale(): def abs_signale(date: datetime, matin: bool, justif: bool, etudid=None, nip=None, ine=None, description=None,
moduleimpl_id=None):
""" """
Retourne un html Permet d'ajouter une absence en base
""" """
return error_response(501, message="Not implemented") # fonction to use : add_absence, add_justif
if description is not None:
if moduleimpl_id is not None:
if etudid is not None:
try:
add_absence(etudid, date, matin, justif, description, moduleimpl_id)
add_justif(etudid, date, matin, description)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
if nip is not None:
etu = models.Identite.query.filter_by(code_nip=nip).first()
try:
add_absence(etu.etudid, date, matin, justif, description, moduleimpl_id)
add_justif(etu.etudid, date, matin, description)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
if ine is not None:
etu = models.Identite.query.filter_by(code_ine=ine).first()
try:
add_absence(etu.etudid, date, matin, justif, description, moduleimpl_id)
add_justif(etu.etudid, date, matin, description)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
return error_response(409, message="La requête ne peut être traitée en létat actuel")
else:
if etudid is not None:
try:
add_absence(etudid, date, matin, justif, description)
add_justif(etudid, date, matin, description)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
if nip is not None:
etu = models.Identite.query.filter_by(code_nip=nip).first()
try:
add_absence(etu.etudid, date, matin, justif, description)
add_justif(etu.etudid, date, matin, description)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
if ine is not None:
etu = models.Identite.query.filter_by(code_ine=ine).first()
try:
add_absence(etu.etudid, date, matin, justif, description)
add_justif(etu.etudid, date, matin, description)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
return error_response(409, message="La requête ne peut être traitée en létat actuel")
else:
if etudid is not None:
try:
add_absence(etudid, date, matin, justif)
add_justif(etudid, date, matin)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
if nip is not None:
etu = models.Identite.query.filter_by(code_nip=nip).first()
try:
add_absence(etu.etudid, date, matin, justif)
add_justif(etu.etudid, date, matin)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
if ine is not None:
etu = models.Identite.query.filter_by(code_ine=ine).first()
try:
add_absence(etu.etudid, date, matin, justif)
add_justif(etu.etudid, date, matin)
except ValueError:
return error_response(409, message="La requête ne peut être traitée en létat actuel")
return error_response(200, message="OK")
@bp.route("/absences/abs_annule?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>", methods=["POST"]) @bp.route("/absences/abs_annule?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
@ -737,7 +830,7 @@ def abs_groupe_etat(
Liste des absences d'un ou plusieurs groupes entre deux dates Liste des absences d'un ou plusieurs groupes entre deux dates
""" """
# list_abs_date< # list_abs_date
return error_response(501, message="Not implemented") return error_response(501, message="Not implemented")
@ -749,6 +842,7 @@ def liste_logos(format="json"):
""" """
Liste des logos définis pour le site scodoc. Liste des logos définis pour le site scodoc.
""" """
# fonction to use : list_logos()
return error_response(501, message="Not implemented") return error_response(501, message="Not implemented")
@ -757,6 +851,7 @@ def recup_logo_global(nom: str):
""" """
Retourne l'image au format png ou jpg Retourne l'image au format png ou jpg
""" """
# fonction to use find_logo
return error_response(501, message="Not implemented") return error_response(501, message="Not implemented")
@ -773,4 +868,5 @@ def recup_logo_dept_global(dept: str, nom: str):
""" """
L'image format png ou jpg L'image format png ou jpg
""" """
# fonction to use find_logo
return error_response(501, message="Not implemented") return error_response(501, message="Not implemented")

52
app/comp/moy_mat.py Normal file
View File

@ -0,0 +1,52 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Calcul des moyennes de matières
"""
# C'est un recalcul (optionnel) effectué _après_ le calcul standard.
import numpy as np
import pandas as pd
from app.comp import moy_ue
from app.models.formsemestre import FormSemestre
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_utils import ModuleType
def compute_mat_moys_classic(
formsemestre: FormSemestre,
sem_matrix: np.array,
ues: list,
modimpl_inscr_df: pd.DataFrame,
modimpl_coefs: np.array,
) -> dict:
"""Calcul des moyennes par matières.
Result: dict, { matiere_id : Series, index etudid }
"""
modimpls_std = [
m
for m in formsemestre.modimpls_sorted
if (m.module.module_type == ModuleType.STANDARD)
and (m.module.ue.type != UE_SPORT)
]
matiere_ids = {m.module.matiere.id for m in modimpls_std}
matiere_moy = {} # { matiere_id : moy pd.Series, index etudid }
for matiere_id in matiere_ids:
modimpl_mask = np.array(
[m.module.matiere.id == matiere_id for m in formsemestre.modimpls_sorted]
)
etud_moy_gen, _, _ = moy_ue.compute_ue_moys_classic(
formsemestre,
sem_matrix=sem_matrix,
ues=ues,
modimpl_inscr_df=modimpl_inscr_df,
modimpl_coefs=modimpl_coefs,
modimpl_mask=modimpl_mask,
)
matiere_moy[matiere_id] = etud_moy_gen
return matiere_moy

View File

@ -27,7 +27,6 @@
"""Fonctions de calcul des moyennes d'UE (classiques ou BUT) """Fonctions de calcul des moyennes d'UE (classiques ou BUT)
""" """
from re import X
import numpy as np import numpy as np
import pandas as pd import pandas as pd

View File

@ -15,7 +15,7 @@ from flask import g, url_for
from app import db from app import db
from app import log from app import log
from app.comp import moy_mod, moy_ue, inscr_mod from app.comp import moy_mat, moy_mod, moy_ue, inscr_mod
from app.comp.res_common import NotesTableCompat from app.comp.res_common import NotesTableCompat
from app.comp.bonus_spo import BonusSport from app.comp.bonus_spo import BonusSport
from app.models import ScoDocSiteConfig from app.models import ScoDocSiteConfig
@ -24,6 +24,7 @@ from app.models.formsemestre import FormSemestre
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.scodoc.sco_codes_parcours import UE_SPORT from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import sco_preferences
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
@ -133,6 +134,10 @@ class ResultatsSemestreClassic(NotesTableCompat):
# --- Classements: # --- Classements:
self.compute_rangs() self.compute_rangs()
# --- En option, moyennes par matières
if sco_preferences.get_preference("bul_show_matieres", self.formsemestre.id):
self.compute_moyennes_matieres()
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float: def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
"""La moyenne de l'étudiant dans le moduleimpl """La moyenne de l'étudiant dans le moduleimpl
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM) Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM)
@ -158,6 +163,16 @@ class ResultatsSemestreClassic(NotesTableCompat):
), ),
} }
def compute_moyennes_matieres(self):
"""Calcul les moyennes par matière. Doit être appelée au besoin, en fin de compute."""
self.moyennes_matieres = moy_mat.compute_mat_moys_classic(
self.formsemestre,
self.sem_matrix,
self.ues,
self.modimpl_inscr_df,
self.modimpl_coefs,
)
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float: def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
"""Détermine le coefficient de l'UE pour cet étudiant. """Détermine le coefficient de l'UE pour cet étudiant.
N'est utilisé que pour l'injection des UE capitalisées dans la N'est utilisé que pour l'injection des UE capitalisées dans la

View File

@ -39,6 +39,7 @@ class ResultatsSemestre(ResultatsCache):
"modimpl_inscr_df", "modimpl_inscr_df",
"modimpls_results", "modimpls_results",
"etud_coef_ue_df", "etud_coef_ue_df",
"moyennes_matieres",
) )
def __init__(self, formsemestre: FormSemestre): def __init__(self, formsemestre: FormSemestre):
@ -57,6 +58,8 @@ class ResultatsSemestre(ResultatsCache):
self.etud_coef_ue_df = None self.etud_coef_ue_df = None
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)""" """coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
self.validations = None self.validations = None
self.moyennes_matieres = {}
"""Moyennes de matières, si calculées. { matiere_id : Series, index etudid }"""
def compute(self): def compute(self):
"Charge les notes et inscriptions et calcule toutes les moyennes" "Charge les notes et inscriptions et calcule toutes les moyennes"
@ -165,7 +168,6 @@ class ResultatsSemestre(ResultatsCache):
""" """
# Supposant qu'il y a peu d'UE capitalisées, # Supposant qu'il y a peu d'UE capitalisées,
# on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée. # on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée.
# return # XXX XXX XXX
if not self.validations: if not self.validations:
self.validations = res_sem.load_formsemestre_validations(self.formsemestre) self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
ue_capitalisees = self.validations.ue_capitalisees ue_capitalisees = self.validations.ue_capitalisees
@ -184,7 +186,9 @@ class ResultatsSemestre(ResultatsCache):
sum_coefs_ue = 0.0 sum_coefs_ue = 0.0
for ue in self.formsemestre.query_ues(): for ue in self.formsemestre.query_ues():
ue_cap = self.get_etud_ue_status(etudid, ue.id) ue_cap = self.get_etud_ue_status(etudid, ue.id)
if ue_cap and ue_cap["is_capitalized"]: if ue_cap is None:
continue
if ue_cap["is_capitalized"]:
recompute_mg = True recompute_mg = True
coef = ue_cap["coef_ue"] coef = ue_cap["coef_ue"]
if not np.isnan(ue_cap["moy"]): if not np.isnan(ue_cap["moy"]):
@ -195,6 +199,12 @@ class ResultatsSemestre(ResultatsCache):
# On doit prendre en compte une ou plusieurs UE capitalisées # On doit prendre en compte une ou plusieurs UE capitalisées
# et donc recalculer la moyenne générale # et donc recalculer la moyenne générale
self.etud_moy_gen[etudid] = sum_notes_ue / sum_coefs_ue self.etud_moy_gen[etudid] = sum_notes_ue / sum_coefs_ue
# Ajoute le bonus sport
if self.bonus is not None and self.bonus[etudid]:
self.etud_moy_gen[etudid] += self.bonus[etudid]
self.etud_moy_gen[etudid] = max(
0.0, min(self.etud_moy_gen[etudid], 20.0)
)
def _get_etud_ue_cap(self, etudid, ue): def _get_etud_ue_cap(self, etudid, ue):
"""""" """"""
@ -510,8 +520,9 @@ class NotesTableCompat(ResultatsSemestre):
def get_etud_mat_moy(self, matiere_id, etudid): def get_etud_mat_moy(self, matiere_id, etudid):
"""moyenne d'un étudiant dans une matière (ou NA si pas de notes)""" """moyenne d'un étudiant dans une matière (ou NA si pas de notes)"""
# non supporté en 9.2 if not self.moyennes_matieres:
return "na" return "nd"
return self.moyennes_matieres[matiere_id][etudid]
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float: def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
"""La moyenne de l'étudiant dans le moduleimpl """La moyenne de l'étudiant dans le moduleimpl

View File

@ -595,11 +595,12 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
"""Description du semestre sous forme de table exportable """Description du semestre sous forme de table exportable
Liste des modules et de leurs coefficients Liste des modules et de leurs coefficients
""" """
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id) use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] F = sco_formations.formation_list(args={"formation_id": formsemestre.formation_id})[
0
]
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"]) parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
Mlist = sco_moduleimpl.moduleimpl_withmodule_list( Mlist = sco_moduleimpl.moduleimpl_withmodule_list(
formsemestre_id=formsemestre_id, sort_by_ue=True formsemestre_id=formsemestre_id, sort_by_ue=True
@ -709,7 +710,7 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
titles["coefficient"] = "Coef. éval." titles["coefficient"] = "Coef. éval."
titles["evalcomplete_str"] = "Complète" titles["evalcomplete_str"] = "Complète"
titles["publish_incomplete_str"] = "Toujours Utilisée" titles["publish_incomplete_str"] = "Toujours Utilisée"
title = "%s %s" % (parcours.SESSION_NAME.capitalize(), sem["titremois"]) title = "%s %s" % (parcours.SESSION_NAME.capitalize(), formsemestre.titre_mois())
return GenTable( return GenTable(
columns_ids=columns_ids, columns_ids=columns_ids,

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.1.61" SCOVERSION = "9.1.62"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"