forked from ScoDoc/ScoDoc
283 lines
10 KiB
Python
283 lines
10 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
#
|
|
##############################################################################
|
|
|
|
"""Rapport sur réussite en BUT pour enquête 2022
|
|
- statistiques decisions
|
|
"""
|
|
from collections import defaultdict
|
|
|
|
|
|
from flask import request
|
|
|
|
from app.but import jury_but
|
|
from app.comp import res_sem
|
|
from app.comp.res_compat import NotesTableCompat
|
|
from app.models import FormSemestre
|
|
from app.models.etudiants import Identite
|
|
from app.models.formsemestre import FormSemestreInscription
|
|
|
|
import app.scodoc.sco_utils as scu
|
|
from app.scodoc import html_sco_header
|
|
from app.scodoc import sco_codes_parcours
|
|
from app.scodoc.sco_exceptions import ScoValueError
|
|
|
|
from app.scodoc import sco_preferences
|
|
import sco_version
|
|
from app.scodoc.gen_tables import GenTable
|
|
|
|
|
|
# Titres, ordonnés
|
|
INDICATEUR_NAMES = {
|
|
"nb_inscr_S1": "Inscrits initiaux S1",
|
|
"nb_dem_S1": "Démissions S1",
|
|
"nb_def_S1": "Défaillants S1",
|
|
"nb_actifs_S1": "Inscrits finals S1",
|
|
"nb_inscr_S2": "Inscrits initiaux S2",
|
|
"nb_dem_S2": "Démissions S2",
|
|
"nb_def_S2": "Défaillants S2",
|
|
"nb_actifs_S2": "Inscrits finals S2",
|
|
"nb_no_decision": "Sans décision de jury BUT",
|
|
"nb_nar": "NAR",
|
|
"nb_passe_manque_rcue": "Passant avec RCUE non validé",
|
|
"nb_red_avec_rcue": "Redoublant avec au moins un RCUE validé",
|
|
"nb_red_sans_rcue": "Redoublant sans avoir validé aucun RCUE",
|
|
"nb_valide_tt_rcue": "Validant tous les RCUE de l'année",
|
|
}
|
|
|
|
|
|
def formsemestre_but_indicateurs(formsemestre_id: int, format="html"):
|
|
"""Page avec tableau indicateurs enquête ADIUT BUT 2022"""
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
|
|
indicateurs_by_bac = but_indicateurs_by_bac(formsemestre)
|
|
# finalement on fait une table avec en ligne
|
|
# les indicateurs, et en colonne les bacs
|
|
bacs = sorted(indicateurs_by_bac.keys())
|
|
|
|
rows = []
|
|
for indicateur, titre_indicateur in INDICATEUR_NAMES.items():
|
|
row = {bac: indicateurs_by_bac[bac][indicateur] for bac in bacs}
|
|
row["titre_indicateur"] = titre_indicateur
|
|
rows.append(row)
|
|
|
|
# ensure Total is the rightmost column
|
|
del bacs[bacs.index("Total")]
|
|
bacs.append("Total")
|
|
|
|
tab = GenTable(
|
|
titles={bac: bac for bac in bacs},
|
|
columns_ids=["titre_indicateur"] + bacs,
|
|
rows=rows,
|
|
html_sortable=False,
|
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
|
filename=scu.make_filename(f"Indicateurs_BUT_{formsemestre.titre_annee()}"),
|
|
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
|
html_caption="Indicateurs BUT annuels.",
|
|
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
|
|
)
|
|
title = "Indicateurs suivi annuel BUT"
|
|
t = tab.make_page(
|
|
title=f"""<h2 class="formsemestre">{title}</h2>""",
|
|
format=format,
|
|
with_html_headers=False,
|
|
)
|
|
if format != "html":
|
|
return t
|
|
H = [
|
|
html_sco_header.sco_header(page_title=title),
|
|
t,
|
|
"""<p class="help">
|
|
</p>""",
|
|
html_sco_header.sco_footer(),
|
|
]
|
|
return "\n".join(H)
|
|
|
|
|
|
def but_indicateurs_by_bac(formsemestre: FormSemestre) -> dict[str:dict]:
|
|
"""
|
|
L'enquête ADIUT porte sur le nombre de
|
|
- inscrits
|
|
- ayant validé tous les RCUE
|
|
- passant en BUT2 sans avoir validé tous les RCUE
|
|
- redoublants avec au moins une RCUE
|
|
- redoublants sans aucune RCUE
|
|
- NAR
|
|
- défaillants
|
|
- démissionnaires,
|
|
le tout découpé en FI/FA et suivant le type de bac : général/techno/pro/autre.
|
|
|
|
Le semestre est FI ou FA, donc on ne traite pas ce point.
|
|
On suppose qu'on est sur un semestre PAIR de BUT, dont les décisions de jury
|
|
ont déjà été saisies.
|
|
"""
|
|
if not formsemestre.formation.is_apc():
|
|
raise ScoValueError(
|
|
"Ce rapport doit être généré à partir d'une formation par compétences (BUT)."
|
|
)
|
|
if formsemestre.semestre_id % 2:
|
|
raise ScoValueError("Ce rapport doit être généré à partir d'un semestre PAIR.")
|
|
# Les semestres suivant/precedent (pour compter les passages et redoublements)
|
|
next_sem_idx = formsemestre.semestre_id + 1
|
|
red_sem_idx = formsemestre.semestre_id - 1
|
|
|
|
# Ventilation par bac
|
|
inscriptions_by_bac = _formsemestre_inscriptions_by_bac(formsemestre)
|
|
indicateurs_by_bac = {}
|
|
decisions_annee_tous = {}
|
|
for bac in inscriptions_by_bac:
|
|
decisions_annee = {
|
|
inscr.etud.id: jury_but.DecisionsProposeesAnnee(inscr.etud, formsemestre)
|
|
for inscr in inscriptions_by_bac[bac]
|
|
}
|
|
indicateurs_by_bac[bac] = _indicateurs_enquete_but(
|
|
inscriptions_by_bac[bac], decisions_annee, red_sem_idx, next_sem_idx
|
|
)
|
|
decisions_annee_tous.update(decisions_annee)
|
|
# refait pour tous
|
|
indicateurs_by_bac["Total"] = _indicateurs_enquete_but(
|
|
formsemestre.inscriptions, decisions_annee_tous, red_sem_idx, next_sem_idx
|
|
)
|
|
# Comptages sur semestre(s) précédent(s)
|
|
# en effet, les étudiants de ce semestre pair peuvent venir de
|
|
# différents S1
|
|
formsemestre_id_precedents = {
|
|
deca.formsemestre_impair.id
|
|
for deca in decisions_annee_tous.values()
|
|
if deca and deca.formsemestre_impair
|
|
}
|
|
for formsemestre_id_precedent in formsemestre_id_precedents:
|
|
formsemestre_impair = FormSemestre.query.get(formsemestre_id_precedent)
|
|
suffix = (
|
|
f"S{formsemestre_impair.semestre_id}"
|
|
if len(formsemestre_id_precedents) == 1
|
|
else formsemestre_impair.session_id()
|
|
)
|
|
for bac, inscriptions in _formsemestre_inscriptions_by_bac(
|
|
formsemestre_impair
|
|
).items():
|
|
if bac not in indicateurs_by_bac:
|
|
indicateurs_by_bac[bac] = defaultdict(lambda: 0)
|
|
indicateurs_by_bac[bac].update(
|
|
_indicateurs_enquete_but_inscrits(inscriptions, suffix)
|
|
)
|
|
indicateurs_by_bac["Total"].update(
|
|
_indicateurs_enquete_but_inscrits(formsemestre_impair.inscriptions, suffix)
|
|
)
|
|
return indicateurs_by_bac
|
|
|
|
|
|
def _formsemestre_inscriptions_by_bac(formsemestre: FormSemestre) -> defaultdict:
|
|
"liste d'inscriptions, par type de bac"
|
|
inscriptions_by_bac = defaultdict(list) # bac : etuds
|
|
for inscr in formsemestre.inscriptions:
|
|
adm = inscr.etud.admission.first()
|
|
bac = adm.get_bac().abbrev() if adm else "?"
|
|
inscriptions_by_bac[bac].append(inscr)
|
|
return inscriptions_by_bac
|
|
|
|
|
|
def _indicateurs_enquete_but_inscrits(
|
|
inscriptions: list[FormSemestreInscription], suffix: str
|
|
) -> dict:
|
|
"""Nombre d'incrits, DEM, DEF.
|
|
Suffixe les clés avec _suffix"""
|
|
if suffix:
|
|
suffix = "_" + suffix
|
|
return defaultdict(
|
|
lambda: "?",
|
|
{
|
|
"nb_inscr" + suffix: len(inscriptions),
|
|
"nb_actifs"
|
|
+ suffix: len([i for i in inscriptions if i.etat == scu.INSCRIT]),
|
|
"nb_def" + suffix: len([i for i in inscriptions if i.etat == scu.DEF]),
|
|
"nb_dem"
|
|
+ suffix: len([i for i in inscriptions if i.etat == scu.DEMISSION]),
|
|
},
|
|
)
|
|
|
|
|
|
def _indicateurs_enquete_but(
|
|
inscriptions: list[FormSemestreInscription],
|
|
decisions_annee: dict[jury_but.DecisionsProposeesAnnee],
|
|
red_sem_idx: int,
|
|
next_sem_idx: int,
|
|
) -> dict:
|
|
"""Calcule les indicateurs de l'enquête ADIUT 2022"""
|
|
indicateurs = _indicateurs_enquete_but_inscrits(inscriptions, suffix="S2")
|
|
indicateurs.update(
|
|
{
|
|
"nb_no_decision": len(
|
|
[True for deca in decisions_annee.values() if deca.code_valide is None]
|
|
),
|
|
"nb_nar": len(
|
|
[
|
|
True
|
|
for deca in decisions_annee.values()
|
|
if deca.code_valide == sco_codes_parcours.NAR
|
|
]
|
|
),
|
|
# Redoublants sans aucune RCUE
|
|
"nb_red_sans_rcue": len(
|
|
[
|
|
True
|
|
for deca in decisions_annee.values()
|
|
if (deca.nb_rcue_valides == 0)
|
|
and (next_sem_idx not in deca.get_autorisations_passage())
|
|
and (red_sem_idx in deca.get_autorisations_passage())
|
|
]
|
|
),
|
|
# Redoublants avec au moins une RCUE
|
|
"nb_red_avec_rcue": len(
|
|
[
|
|
True
|
|
for deca in decisions_annee.values()
|
|
if (deca.nb_rcue_valides > 0)
|
|
and (next_sem_idx not in deca.get_autorisations_passage())
|
|
and (red_sem_idx in deca.get_autorisations_passage())
|
|
]
|
|
),
|
|
# Passant (en BUT2) sans avoir validé tous les RCUE
|
|
"nb_passe_manque_rcue": len(
|
|
[
|
|
True
|
|
for deca in decisions_annee.values()
|
|
if (deca.nb_rcue_valides < deca.nb_competences)
|
|
and (next_sem_idx in deca.get_autorisations_passage())
|
|
]
|
|
),
|
|
# Ayant validé tous les RCUE
|
|
"nb_valide_tt_rcue": len(
|
|
[
|
|
True
|
|
for deca in decisions_annee.values()
|
|
if (deca.nb_rcue_valides >= deca.nb_competences)
|
|
]
|
|
),
|
|
}
|
|
)
|
|
return indicateurs
|