feat: add heatmap to resultat

This commit is contained in:
2025-08-10 06:27:36 +02:00
parent 4f8ab0925b
commit 13f0e69bb0
4 changed files with 1177 additions and 5 deletions

View File

@@ -187,6 +187,7 @@ def new():
@bp.route('/<int:id>/results')
@handle_db_errors
def results(id):
from models import Competence, Domain
assessment_repo = AssessmentRepository()
assessment = assessment_repo.get_with_full_details_or_404(id)
@@ -204,12 +205,228 @@ def results(id):
# Préparer les données pour l'histogramme
scores = [data['total_score'] for data in students_scores.values()]
# === NOUVEAUX : Préparer les données pour les heatmaps ===
# Récupérer toutes les compétences et domaines avec leurs couleurs depuis la BD
all_competences = {comp.name: comp.color for comp in Competence.query.all()}
all_domains = {domain.id: {'name': domain.name, 'color': domain.color} for domain in Domain.query.all()}
# Collecter toutes les compétences et domaines présents dans cette évaluation
competences_in_eval = set()
domains_in_eval = set()
for exercise in assessment.exercises:
for element in exercise.grading_elements:
if element.skill:
competences_in_eval.add(element.skill)
if element.domain_id:
domains_in_eval.add(element.domain_id)
# Préparer les données heatmap compétences
students_list = [f"{s['student'].last_name} {s['student'].first_name}" for s in sorted_students]
competences_list = sorted(competences_in_eval)
# Calculer les scores par élève/compétence
competences_scores_matrix = []
for student_data in sorted_students:
student_scores_by_competence = {}
student_totals_by_competence = {}
for exercise_id, exercise_data in student_data['exercises'].items():
# Récupérer l'exercice pour accéder aux grading_elements
exercise = next(ex for ex in assessment.exercises if ex.id == exercise_id)
for element in exercise.grading_elements:
if element.skill and element.skill in competences_in_eval:
# Trouver la note correspondante
grade = None
for g in element.grades:
if g.student_id == student_data['student'].id:
grade = g
break
if grade and grade.value:
from models import GradingCalculator
score = GradingCalculator.calculate_score(grade.value, element.grading_type, element.max_points)
if score is not None: # Exclure les dispensés
if element.skill not in student_scores_by_competence:
student_scores_by_competence[element.skill] = 0
student_totals_by_competence[element.skill] = 0
student_scores_by_competence[element.skill] += score
student_totals_by_competence[element.skill] += element.max_points
# Calculer les pourcentages par compétence pour cet élève
student_row = []
for comp in competences_list:
if comp in student_scores_by_competence and student_totals_by_competence[comp] > 0:
percentage = (student_scores_by_competence[comp] / student_totals_by_competence[comp]) * 100
student_row.append(round(percentage, 1))
else:
student_row.append(None) # Pas de données pour cette compétence
competences_scores_matrix.append(student_row)
# Préparer les données heatmap domaines
domains_list = []
domains_colors = {}
# Trier les domain_id (entiers) puis récupérer les noms
sorted_domain_ids = sorted(domains_in_eval)
for domain_id in sorted_domain_ids:
if domain_id in all_domains:
domain_name = all_domains[domain_id]['name']
domains_list.append(domain_name)
domains_colors[domain_name] = all_domains[domain_id]['color']
# Calculer les scores par élève/domaine
domains_scores_matrix = []
for student_data in sorted_students:
student_scores_by_domain = {}
student_totals_by_domain = {}
for exercise_id, exercise_data in student_data['exercises'].items():
# Récupérer l'exercice pour accéder aux grading_elements
exercise = next(ex for ex in assessment.exercises if ex.id == exercise_id)
for element in exercise.grading_elements:
if element.domain_id and element.domain_id in domains_in_eval:
domain_name = all_domains[element.domain_id]['name']
# Trouver la note correspondante
grade = None
for g in element.grades:
if g.student_id == student_data['student'].id:
grade = g
break
if grade and grade.value:
from models import GradingCalculator
score = GradingCalculator.calculate_score(grade.value, element.grading_type, element.max_points)
if score is not None: # Exclure les dispensés
if domain_name not in student_scores_by_domain:
student_scores_by_domain[domain_name] = 0
student_totals_by_domain[domain_name] = 0
student_scores_by_domain[domain_name] += score
student_totals_by_domain[domain_name] += element.max_points
# Calculer les pourcentages par domaine pour cet élève
student_row = []
for domain in domains_list:
if domain in student_scores_by_domain and student_totals_by_domain[domain] > 0:
percentage = (student_scores_by_domain[domain] / student_totals_by_domain[domain]) * 100
student_row.append(round(percentage, 1))
else:
student_row.append(None) # Pas de données pour ce domaine
domains_scores_matrix.append(student_row)
# Préparer les couleurs des compétences pour celles présentes dans l'évaluation
competences_colors = {comp: all_competences.get(comp, '#6b7280') for comp in competences_list}
heatmap_competences = {
'students': students_list,
'competences': competences_list,
'scores': competences_scores_matrix,
'colors': competences_colors
} if competences_list else None
heatmap_domains = {
'students': students_list,
'domains': domains_list,
'scores': domains_scores_matrix,
'colors': domains_colors
} if domains_list else None
# === NOUVEAU : Préparer les données heatmap éléments de notation ===
# Collecter tous les éléments de notation de l'évaluation
grading_elements_list = []
grading_elements_info = {}
for exercise in sorted(assessment.exercises, key=lambda x: x.order):
for element in exercise.grading_elements:
element_key = f"{exercise.title} - {element.label}"
grading_elements_list.append(element_key)
grading_elements_info[element_key] = {
'element': element,
'exercise': exercise
}
# Collecter les données détaillées par élève/élément avec valeurs originales
grading_elements_detailed_matrix = []
for student_data in sorted_students:
student_row = []
for element_key in grading_elements_list:
element_info = grading_elements_info[element_key]
element = element_info['element']
# Trouver la note correspondante
grade = None
for g in element.grades:
if g.student_id == student_data['student'].id:
grade = g
break
if grade and grade.value:
# Stocker les données complètes pour le rendu avec couleurs spécifiques
student_row.append({
'value': grade.value,
'grading_type': element.grading_type,
'max_points': element.max_points
})
else:
student_row.append(None) # Pas de note
grading_elements_detailed_matrix.append(student_row)
# Préparer les couleurs des éléments basées sur les exercices pour une meilleure distinction
grading_elements_colors = {}
exercise_colors = [
'#3b82f6', # Bleu
'#10b981', # Vert
'#f59e0b', # Orange
'#8b5cf6', # Violet
'#ef4444', # Rouge
'#06b6d4', # Cyan
'#84cc16', # Vert clair
'#f97316', # Orange foncé
]
exercises_seen = {}
color_index = 0
for element_key in grading_elements_list:
element_info = grading_elements_info[element_key]
exercise = element_info['exercise']
# Assigner une couleur unique par exercice
if exercise.id not in exercises_seen:
exercises_seen[exercise.id] = exercise_colors[color_index % len(exercise_colors)]
color_index += 1
grading_elements_colors[element_key] = exercises_seen[exercise.id]
# Récupérer la configuration des couleurs depuis la base de données
from app_config import config_manager
scale_colors = config_manager.get_competence_scale_values()
heatmap_grading_elements = {
'students': students_list,
'elements': grading_elements_list,
'detailed_scores': grading_elements_detailed_matrix,
'colors': grading_elements_colors,
'scale_colors': scale_colors
} if grading_elements_list else None
return render_template('assessment_results.html',
assessment=assessment,
students_scores=sorted_students,
statistics=statistics,
total_max_points=total_max_points,
scores_json=scores)
scores_json=scores,
heatmap_competences=heatmap_competences,
heatmap_domains=heatmap_domains,
heatmap_grading_elements=heatmap_grading_elements)
@bp.route('/<int:id>/delete', methods=['POST'])
@handle_db_errors