feat: intégrer la configuration des compétences dans la gestion des assessments

- Remplacer le champ texte libre par une liste déroulante des compétences configurées
- Charger dynamiquement les compétences dans les routes d'assessments (new/edit)
- Moderniser le calcul des scores pour utiliser l'échelle de compétences configurable
- Adapter la logique de scoring aux valeurs personnalisées (0-3 ou autres)
- Respecter le paramètre 'included_in_total' de chaque valeur de l'échelle
- Maintenir la compatibilité descendante avec l'ancienne formule

🎯 Améliore l'intégration entre la configuration système et l'interface utilisateur
📊 Rend les calculs de scores plus flexibles et cohérents avec la configuration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-05 06:13:28 +02:00
parent b08cc2aba4
commit 6de8dc066f
3 changed files with 61 additions and 10 deletions

View File

@@ -112,6 +112,10 @@ class Assessment(db.Model):
Retourne un dictionnaire avec les scores par élève et par exercice.""" Retourne un dictionnaire avec les scores par élève et par exercice."""
from collections import defaultdict from collections import defaultdict
import statistics import statistics
from app_config import config_manager
# Récupérer l'échelle des compétences configurée
competence_scale = config_manager.get_competence_scale_values()
students_scores = {} students_scores = {}
exercise_scores = defaultdict(lambda: defaultdict(float)) exercise_scores = defaultdict(lambda: defaultdict(float))
@@ -144,16 +148,44 @@ class Assessment(db.Model):
exercise_max_points += element.max_points exercise_max_points += element.max_points
except ValueError: except ValueError:
pass pass
else: # compétences else: # compétences - utiliser la nouvelle échelle
if grade.value == '.': grade_value = grade.value.strip()
# '.' signifie non évalué = 0 point mais on compte le max
exercise_score += 0 # Gérer les valeurs numériques et string
exercise_max_points += (1/3) * 3 * element.max_points # Score max de 3 scale_key = int(grade_value) if grade_value.isdigit() else grade_value
if scale_key in competence_scale:
scale_config = competence_scale[scale_key]
if scale_config['included_in_total']:
# Calculer le score selon l'échelle configurée
if grade_value == '.':
# Non évalué = 0 point
exercise_score += 0
else:
# Calculer le score proportionnel
# Trouver la valeur maximale de l'échelle
max_scale_value = max([
int(k) if str(k).isdigit() else 0
for k in competence_scale.keys()
if competence_scale[k]['included_in_total'] and k != '.'
])
if max_scale_value > 0:
if grade_value.isdigit():
score_ratio = int(grade_value) / max_scale_value
exercise_score += score_ratio * element.max_points
# Compter les points maximum (sauf pour '.')
if grade_value != '.':
exercise_max_points += element.max_points
# Si not included_in_total, on ne compte ni score ni max
else: else:
# Valeur non reconnue, utiliser l'ancienne logique par défaut
try: try:
score_value = float(grade.value) score_value = float(grade_value)
exercise_score += (1/3) * score_value * element.max_points exercise_score += (1/3) * score_value * element.max_points
exercise_max_points += (1/3) * 3 * element.max_points # Score max de 3 exercise_max_points += element.max_points
except ValueError: except ValueError:
pass pass

View File

@@ -160,23 +160,35 @@ def edit(id):
exercise_data['grading_elements'].append(element_data) exercise_data['grading_elements'].append(element_data)
exercises_data.append(exercise_data) exercises_data.append(exercise_data)
# Récupérer les compétences configurées
from app_config import config_manager
competences = config_manager.get_competences_list()
return render_template('assessment_form_unified.html', return render_template('assessment_form_unified.html',
form=form, form=form,
title='Modifier l\'évaluation complète', title='Modifier l\'évaluation complète',
assessment=assessment, assessment=assessment,
exercises_json=exercises_data, exercises_json=exercises_data,
is_edit=True) is_edit=True,
competences=competences)
@bp.route('/new', methods=['GET', 'POST']) @bp.route('/new', methods=['GET', 'POST'])
@handle_db_errors @handle_db_errors
def new(): def new():
from app_config import config_manager
form = AssessmentForm() form = AssessmentForm()
result = _handle_unified_assessment_request(form, is_edit=False) result = _handle_unified_assessment_request(form, is_edit=False)
if result: if result:
return result return result
return render_template('assessment_form_unified.html', form=form, title='Nouvelle évaluation complète') # Récupérer les compétences configurées
competences = config_manager.get_competences_list()
return render_template('assessment_form_unified.html',
form=form,
title='Nouvelle évaluation complète',
competences=competences)
@bp.route('/<int:id>/results') @bp.route('/<int:id>/results')
@handle_db_errors @handle_db_errors

View File

@@ -241,7 +241,14 @@
</div> </div>
<div> <div>
<label class="block text-xs font-medium text-gray-700 mb-1">Compétence</label> <label class="block text-xs font-medium text-gray-700 mb-1">Compétence</label>
<input type="text" class="element-skill block w-full border border-gray-300 rounded-md px-2 py-1 text-sm focus:ring-purple-500 focus:border-purple-500"> <select class="element-skill block w-full border border-gray-300 rounded-md px-2 py-1 text-sm focus:ring-purple-500 focus:border-purple-500">
<option value="">Non spécifiée</option>
{% for competence in competences %}
<option value="{{ competence.name }}" data-color="{{ competence.color }}" data-icon="{{ competence.icon }}">
{{ competence.name }}
</option>
{% endfor %}
</select>
</div> </div>
<div> <div>
<label class="block text-xs font-medium text-gray-700 mb-1">Points max</label> <label class="block text-xs font-medium text-gray-700 mb-1">Points max</label>