# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2023 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
#
##############################################################################
"""Comparaison de deux fichiers Apogée (maquettes)
1) Vérifier:
etape_apogee, vdi_apogee, cod_dip_apogee, annee_scolaire
structure: col_ids (la comparaison portera sur les colonnes communes)
2) Comparer listes d'étudiants
Présents dans A mais pas dans B
Présents dans B mais pas dans A
nombre communs
3) Comparer résultats
Pour chaque étudiant commun:
Pour chaque colonne commune:
comparer les résultats
"""
from flask import g, url_for
from app import log
from app.scodoc import sco_apogee_csv
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import html_sco_header
from app.scodoc import sco_preferences
_help_txt = """
Outil de comparaison de fichiers (maquettes CSV) Apogée.
Cet outil compare deux fichiers fournis. Aucune donnée stockée dans ScoDoc n'est utilisée.
"""
def apo_compare_csv_form():
"""Form: submit 2 CSV files to compare them."""
H = [
html_sco_header.sco_header(page_title="Comparaison de fichiers Apogée"),
"""Comparaison de fichiers Apogée
""",
html_sco_header.sco_footer(),
]
return "\n".join(H)
def apo_compare_csv(file_a, file_b, autodetect=True):
"""Page comparing 2 Apogee CSV files"""
try:
apo_data_a = _load_apo_data(file_a, autodetect=autodetect)
apo_data_b = _load_apo_data(file_b, autodetect=autodetect)
except (UnicodeDecodeError, UnicodeEncodeError) as exc:
dest_url = url_for("notes.semset_page", scodoc_dept=g.scodoc_dept)
if autodetect:
raise ScoValueError(
"""
Erreur: l'encodage de l'un des fichiers est mal détecté.
Essayez sans auto-détection, ou vérifiez le codage et le contenu
des fichiers.
""",
dest_url=dest_url,
) from exc
else:
raise ScoValueError(
f"""
Erreur: l'encodage de l'un des fichiers est incorrect.
Vérifiez qu'il est bien en {sco_apogee_csv.APO_INPUT_ENCODING}
""",
dest_url=dest_url,
) from exc
H = [
html_sco_header.sco_header(page_title="Comparaison de fichiers Apogée"),
"Comparaison de fichiers Apogée
",
_help_txt,
'',
_apo_compare_csv(apo_data_a, apo_data_b),
"
",
"""Autre comparaison
""",
html_sco_header.sco_footer(),
]
return "\n".join(H)
def _load_apo_data(csvfile, autodetect=True):
"Read data from request variable and build ApoData"
data_b = csvfile.read()
if autodetect:
data_b, message = sco_apogee_csv.fix_data_encoding(data_b)
if message:
log(f"apo_compare_csv: {message}")
if not data_b:
raise ScoValueError("fichier vide ? (apo_compare_csv: no data)")
data = data_b.decode(sco_apogee_csv.APO_INPUT_ENCODING)
apo_data = sco_apogee_csv.ApoData(data, orig_filename=csvfile.filename)
return apo_data
def _apo_compare_csv(A, B):
"""Generate html report comparing A and B, two instances of ApoData
representing Apogee CSV maquettes.
"""
L = []
# 1-- Check etape and codes
L.append('En-tête
')
L.append('
Nom fichier A:')
L.append(A.orig_filename)
L.append("
")
L.append('
Nom fichier B:')
L.append(B.orig_filename)
L.append("
")
L.append('
Étape Apogée:')
if A.etape_apogee != B.etape_apogee:
L.append(
'%s != %s' % (A.etape_apogee, B.etape_apogee)
)
else:
L.append('%s' % (A.etape_apogee,))
L.append("
")
L.append('
VDI Apogée:')
if A.vdi_apogee != B.vdi_apogee:
L.append('%s != %s' % (A.vdi_apogee, B.vdi_apogee))
else:
L.append('%s' % (A.vdi_apogee,))
L.append("
")
L.append('
Code diplôme :')
if A.cod_dip_apogee != B.cod_dip_apogee:
L.append(
'%s != %s'
% (A.cod_dip_apogee, B.cod_dip_apogee)
)
else:
L.append('%s' % (A.cod_dip_apogee,))
L.append("
")
L.append('
Année scolaire :')
if A.annee_scolaire != B.annee_scolaire:
L.append(
'%s != %s'
% (A.annee_scolaire, B.annee_scolaire)
)
else:
L.append('%s' % (A.annee_scolaire,))
L.append("
")
# Colonnes:
A_elts = set(A.apo_elts.keys())
B_elts = set(B.apo_elts.keys())
L.append('
Éléments Apogée :')
if A_elts == B_elts:
L.append('
%d' % len(A_elts))
else:
elts_communs = A_elts.intersection(B_elts)
elts_only_A = A_elts - A_elts.intersection(B_elts)
elts_only_B = B_elts - A_elts.intersection(B_elts)
L.append(
'
différents (%d en commun, %d seulement dans A, %d seulement dans B)'
% (
len(elts_communs),
len(elts_only_A),
len(elts_only_B),
)
)
if elts_only_A:
L.append(
'
Éléments seulement dans A : %s
'
% ", ".join(sorted(elts_only_A))
)
if elts_only_B:
L.append(
'
Éléments seulement dans B : %s
'
% ", ".join(sorted(elts_only_B))
)
L.append("
")
L.append("
") # /section
# 2--
L.append('Étudiants
')
A_nips = set(A.etud_by_nip)
B_nips = set(B.etud_by_nip)
nb_etuds_communs = len(A_nips.intersection(B_nips))
nb_etuds_dif = len(A_nips.union(B_nips) - A_nips.intersection(B_nips))
L.append("""
Liste d'étudiants :""")
if A_nips == B_nips:
L.append(
"""
%d étudiants (tous présents dans chaque fichier)
"""
% len(A_nips)
)
else:
L.append(
'différents (%d en commun, %d différents)'
% (nb_etuds_communs, nb_etuds_dif)
)
L.append("
")
L.append("
") # /section
# 3-- Résultats de chaque étudiant:
if nb_etuds_communs > 0:
L.append(
"""
Différences de résultats des étudiants présents dans les deux fichiers
"""
)
T = apo_table_compare_etud_results(A, B)
if T.get_nb_rows() > 0:
L.append(T.html())
else:
L.append(
"""
aucune différence de résultats
sur les %d étudiants communs (les éléments Apogée n'apparaissant pas dans les deux fichiers sont omis)
"""
% nb_etuds_communs
)
L.append("
") # /section
return "\n".join(L)
def apo_table_compare_etud_results(A, B):
""""""
D = compare_etuds_res(A, B)
T = GenTable(
rows=D,
titles={
"nip": "NIP",
"nom": "Nom",
"prenom": "Prénom",
"elt_code": "Element",
"type_res": "Type",
"val_A": "A: %s" % (A.orig_filename or ""),
"val_B": "B: %s" % (B.orig_filename or ""),
},
columns_ids=("nip", "nom", "prenom", "elt_code", "type_res", "val_A", "val_B"),
html_class="table_leftalign",
html_with_td_classes=True,
preferences=sco_preferences.SemPreferences(),
)
return T
def _build_etud_res(e, apo_data):
r = {}
for elt_code in apo_data.apo_elts:
elt = apo_data.apo_elts[elt_code]
try:
# les colonnes de cet élément
col_ids_type = [
(ec["apoL_a01_code"], ec["Type R\xc3\xa9s."]) for ec in elt.cols
]
except KeyError as exc:
raise ScoValueError(
"Erreur: un élément sans 'Type R\xc3\xa9s.'. Vérifiez l'encodage de vos fichiers."
) from exc
r[elt_code] = {}
for (col_id, type_res) in col_ids_type:
r[elt_code][type_res] = e.cols[col_id]
return r
def compare_etud_res(r_A, r_B, remove_missing=True):
"""Pour chaque valeur difference dans les resultats d'un etudiant
elt_code type_res val_A val_B
"""
diffs = []
elt_codes = set(r_A).union(set(r_B))
for elt_code in elt_codes:
for type_res in r_A.get(elt_code, r_B.get(elt_code)):
if elt_code not in r_A:
if remove_missing:
continue
else:
val_A = None # element absent
else:
val_A = r_A[elt_code][type_res]
if elt_code not in r_B:
if remove_missing:
continue
else:
val_B = None # element absent
else:
val_B = r_B[elt_code][type_res]
if type_res == "N":
# Cas particulier pour les notes: compare les nombres
try:
val_A_num = float(val_A.replace(",", "."))
val_B_num = float(val_B.replace(",", "."))
except ValueError:
val_A_num, val_B_num = val_A, val_B
val_A, val_B = val_A_num, val_B_num
if val_A != val_B:
diffs.append(
{
"elt_code": elt_code,
"type_res": type_res,
"val_A": val_A,
"val_B": val_B,
}
)
return diffs
def compare_etuds_res(A, B):
"""
nip, nom, prenom, elt_code, type_res, val_A, val_B
"""
A_nips = set(A.etud_by_nip)
B_nips = set(B.etud_by_nip)
common_nips = A_nips.intersection(B_nips)
# A_not_B_nips = A_nips - B_nips
# B_not_A_nips = B_nips - A_nips
D = []
for nip in common_nips:
etu_A = A.etud_by_nip[nip]
etu_B = B.etud_by_nip[nip]
r_A = _build_etud_res(etu_A, A)
r_B = _build_etud_res(etu_B, B)
diffs = compare_etud_res(r_A, r_B)
for d in diffs:
d.update(
{"nip": etu_A["nip"], "nom": etu_A["nom"], "prenom": etu_A["prenom"]}
)
D.append(d)
return D