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."""
from collections import defaultdict
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 = {}
exercise_scores = defaultdict(lambda: defaultdict(float))
@@ -144,16 +148,44 @@ class Assessment(db.Model):
exercise_max_points += element.max_points
except ValueError:
pass
else: # compétences
if grade.value == '.':
# '.' signifie non évalué = 0 point mais on compte le max
exercise_score += 0
exercise_max_points += (1/3) * 3 * element.max_points # Score max de 3
else: # compétences - utiliser la nouvelle échelle
grade_value = grade.value.strip()
# Gérer les valeurs numériques et string
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:
# Valeur non reconnue, utiliser l'ancienne logique par défaut
try:
score_value = float(grade.value)
score_value = float(grade_value)
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:
pass

View File

@@ -160,23 +160,35 @@ def edit(id):
exercise_data['grading_elements'].append(element_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',
form=form,
title='Modifier l\'évaluation complète',
assessment=assessment,
exercises_json=exercises_data,
is_edit=True)
is_edit=True,
competences=competences)
@bp.route('/new', methods=['GET', 'POST'])
@handle_db_errors
def new():
from app_config import config_manager
form = AssessmentForm()
result = _handle_unified_assessment_request(form, is_edit=False)
if 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')
@handle_db_errors

View File

@@ -241,7 +241,14 @@
</div>
<div>
<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>
<label class="block text-xs font-medium text-gray-700 mb-1">Points max</label>