DocScoDoc/app/scodoc/sco_apogee_compare.py

348 lines
12 KiB
Python

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 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 collections import OrderedDict
import sco_utils as scu
from notes_log import log
import sco_apogee_csv
from gen_tables import GenTable
from sco_exceptions import ScoValueError
_help_txt = """
<div class="help">
<p>Outil de comparaison de fichiers (maquettes CSV) Apogée.
</p>
<p>Cet outil compare deux fichiers fournis. Aucune donnée stockée dans ScoDoc n'est utilisée.
</p>
</div>
"""
def apo_compare_csv_form(context, REQUEST=None):
"""Form: submit 2 CSV files to compare them."""
H = [
html_sco_header.sco_header(context, REQUEST, page_title="Comparaison de fichiers Apogée"),
"""<h2>Comparaison de fichiers Apogée</h2>
<form id="apo_csv_add" action="apo_compare_csv" method="post" enctype="multipart/form-data">
""",
_help_txt,
"""
<div class="apo_compare_csv_form_but">
Fichier Apogée A:
<input type="file" size="30" name="A_file"/>
</div>
<div class="apo_compare_csv_form_but">
Fichier Apogée B:
<input type="file" size="30" name="B_file"/>
</div>
<input type="checkbox" name="autodetect" checked/>autodétecter encodage</input>
<div class="apo_compare_csv_form_submit">
<input type="submit" value="Comparer ces fichiers"/>
</div>
</form>""",
html_sco_header.sco_footer(context, REQUEST),
]
return "\n".join(H)
def apo_compare_csv(context, A_file, B_file, autodetect=True, REQUEST=None):
"""Page comparing 2 Apogee CSV files"""
A = _load_apo_data(A_file, autodetect=autodetect)
B = _load_apo_data(B_file, autodetect=autodetect)
H = [
html_sco_header.sco_header(context, REQUEST, page_title="Comparaison de fichiers Apogée"),
"<h2>Comparaison de fichiers Apogée</h2>",
_help_txt,
'<div class="apo_compare_csv">',
_apo_compare_csv(context, A, B, REQUEST=None),
"</div>",
"""<p><a href="apo_compare_csv_form" class="stdlink">Autre comparaison</a></p>""",
html_sco_header.sco_footer(context, REQUEST),
]
return "\n".join(H)
def _load_apo_data(csvfile, autodetect=True):
"Read data from request variable and build ApoData"
data = csvfile.read()
if autodetect:
data, message = sco_apogee_csv.fix_data_encoding(data)
if message:
log("apo_compare_csv: %s" % message)
if not data:
raise ScoValueError("apo_compare_csv: no data")
apo_data = sco_apogee_csv.ApoData(data, orig_filename=csvfile.filename)
return apo_data
def _apo_compare_csv(context, A, B, REQUEST=None):
"""Generate html report comparing A and B, two instances of ApoData
representing Apogee CSV maquettes.
"""
L = []
# 1-- Check etape and codes
L.append('<div class="section"><div class="tit">En-tête</div>')
L.append('<div><span class="key">Nom fichier A:</span><span class="val_ok">')
L.append(A.orig_filename)
L.append("</span></div>")
L.append('<div><span class="key">Nom fichier B:</span><span class="val_ok">')
L.append(B.orig_filename)
L.append("</span></div>")
L.append('<div><span class="key">Étape Apogée:</span>')
if A.etape_apogee != B.etape_apogee:
L.append(
'<span class="val_dif">%s != %s</span>' % (A.etape_apogee, B.etape_apogee)
)
else:
L.append('<span class="val_ok">%s</span>' % (A.etape_apogee,))
L.append("</div>")
L.append('<div><span class="key">VDI Apogée:</span>')
if A.vdi_apogee != B.vdi_apogee:
L.append('<span class="val_dif">%s != %s</span>' % (A.vdi_apogee, B.vdi_apogee))
else:
L.append('<span class="val_ok">%s</span>' % (A.vdi_apogee,))
L.append("</div>")
L.append('<div><span class="key">Code diplôme :</span>')
if A.cod_dip_apogee != B.cod_dip_apogee:
L.append(
'<span class="val_dif">%s != %s</span>'
% (A.cod_dip_apogee, B.cod_dip_apogee)
)
else:
L.append('<span class="val_ok">%s</span>' % (A.cod_dip_apogee,))
L.append("</div>")
L.append('<div><span class="key">Année scolaire :</span>')
if A.annee_scolaire != B.annee_scolaire:
L.append(
'<span class="val_dif">%s != %s</span>'
% (A.annee_scolaire, B.annee_scolaire)
)
else:
L.append('<span class="val_ok">%s</span>' % (A.annee_scolaire,))
L.append("</div>")
# Colonnes:
A_elts = set(A.apo_elts.keys())
B_elts = set(B.apo_elts.keys())
L.append('<div><span class="key">Éléments Apogée :</span>')
if A_elts == B_elts:
L.append('<span class="val_ok">%d</span>' % 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(
'<span class="val_dif">différents (%d en commun, %d seulement dans A, %d seulement dans B)</span>'
% (
len(elts_communs),
len(elts_only_A),
len(elts_only_B),
)
)
if elts_only_A:
L.append(
'<div span class="key">Éléments seulement dans A : </span><span class="val_dif">%s</span></div>'
% ", ".join(sorted(elts_only_A))
)
if elts_only_B:
L.append(
'<div span class="key">Éléments seulement dans B : </span><span class="val_dif">%s</span></div>'
% ", ".join(sorted(elts_only_B))
)
L.append("</div>")
L.append("</div>") # /section
# 2--
L.append('<div class="section"><div class="tit">Étudiants</div>')
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("""<div><span class="key">Liste d'étudiants :</span>""")
if A_nips == B_nips:
L.append(
"""<span class="s_ok">
%d étudiants (tous présents dans chaque fichier)</span>
"""
% len(A_nips)
)
else:
L.append(
'<span class="val_dif">différents (%d en commun, %d différents)</span>'
% (nb_etuds_communs, nb_etuds_dif)
)
L.append("</div>")
L.append("</div>") # /section
# 3-- Résultats de chaque étudiant:
if nb_etuds_communs > 0:
L.append(
"""<div class="section sec_table">
<div class="tit">Différences de résultats des étudiants présents dans les deux fichiers</div>
<p>
"""
)
T = apo_table_compare_etud_results(context, A, B, REQUEST=REQUEST)
if T.get_nb_rows() > 0:
L.append(T.html())
else:
L.append(
"""<p class="p_ok">aucune différence de résultats
sur les %d étudiants communs (<em>les éléments Apogée n'apparaissant pas dans les deux fichiers sont omis</em>)</p>
"""
% nb_etuds_communs
)
L.append("</div>") # /section
return "\n".join(L)
def apo_table_compare_etud_results(context, A, B, REQUEST=None):
""""""
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=context.get_preferences(),
)
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]
col_ids_type = [
(ec["apoL_a01_code"], ec["Type R\xc3\xa9s."]) for ec in elt.cols
] # les colonnes de cet élément
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