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:
46
models.py
46
models.py
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user