Système d'étoiles¶
À la fin d'un parcours, l'étudiant reçoit un nombre d'étoiles qui
récompense à la fois la difficulté du niveau atteint et le respect
du temps imparti. Ce score est affiché sur la page de succès
(/s/:token) et utilisé par le panneau formateur pour ordonner les
résultats dans le récapitulatif de session.
La méthode computeStars¶
Le calcul est centralisé dans ChaosStudentController.computeStars() :
static int computeStars(String level, boolean inTime) {
int base = switch (level != null ? level : "") {
case "bac1" -> 1;
case "bac2" -> 2;
case "bac3" -> 3;
case "bac4" -> 4;
case "bac5" -> 5;
default -> 1;
};
return inTime ? base : Math.max(1, base - 1);
}
Deux principes simples se combinent :
- Base = numéro du niveau.
bac1rapporte au plus 1 étoile,bac5au plus 5. L'étoile maximale pour un niveau donné est une constante, pas une variable de difficulté dynamique. - Malus d'une étoile en cas de dépassement. Si la session n'est
pas complétée dans le temps imparti (
durationSeconds > pedagogiqueTimer), on soustrait 1 étoile, avec un plancher à 1 étoile — il est impossible de retomber à 0, même sur un parcours terminé plusieurs heures après le délai.
Tableau des scores possibles¶
| Niveau | Durée imparti | Étoiles si dans les temps | Étoiles si hors temps |
|---|---|---|---|
bac1 |
30 min | 1 ⭐ | 1 ⭐ |
bac2 |
45 min | 2 ⭐⭐ | 1 ⭐ |
bac3 |
60 min | 3 ⭐⭐⭐ | 2 ⭐⭐ |
bac4 |
75 min | 4 ⭐⭐⭐⭐ | 3 ⭐⭐⭐ |
bac5 |
90 min | 5 ⭐⭐⭐⭐⭐ | 4 ⭐⭐⭐⭐ |
Pourquoi bac1 ne perd jamais d'étoile
Le plancher à 1 étoile garantit que le niveau bac1 — pensé comme
un parcours d'initiation — ne « punit » pas un étudiant qui prend
le temps d'explorer la boutique sans stress. Un dépassement sur
bac1 rend la durée visible au formateur mais conserve la
récompense symbolique.
Calcul du inTime¶
Le booléen inTime est calculé au moment où le parcours est complété,
dans validatePedagogique() :
Point important : la comparaison utilise le temps absolu écoulé
depuis le démarrage du parcours par le formateur (pedagogiqueStartTime),
pas le temps depuis que l'étudiant individuel a rejoint la session
(session.joinedAt). Un étudiant qui rejoint la session 10 minutes
après le /activate dispose donc de 20 minutes pour un bac1 et
non de 30, car son décompte partage la même base temporelle que celui
du formateur.
Ce choix est pédagogique : le formateur peut démarrer un parcours et laisser les étudiants le rejoindre progressivement, mais le « tous en même temps » force les retardataires à accélérer — ce qui correspond à la réalité d'une classe.
Fallback pour completedAt = 0¶
Dans le bloc buildPedagogiqueState() qui sert les polls /status,
un garde-fou gère le cas où completedAt = 0 alors que l'étape finale
est marquée comme terminée :
int stars;
if (session.completedAt > 0) {
long dur = (session.completedAt - session.joinedAt) / 1000L;
stars = computeStars(pedagogiqueLevel, dur <= pedagogiqueTimer);
} else {
log.warn("[Pédagogique] completedAt=0 pour {} — fallback temps global",
session.displayAlias());
stars = computeStars(pedagogiqueLevel, elapsed <= pedagogiqueTimer);
}
Si completedAt est à zéro (situation anormale — une session qui a
traversé un redémarrage mal nettoyé, par exemple), le calcul repasse
sur le temps global du formateur. Un warning est tracé pour alerter
l'équipe d'exploitation.
Propagation du score¶
sequenceDiagram
autonumber
actor E as Étudiant
participant OVR as PedagogiqueOverlay
participant CTR as ChaosStudentController
participant DB as MySQL
participant SUC as PedagogiqueSucces
participant ADM as chaos-admin
Note over E,OVR: Dernière étape DYNAMIC validée
OVR->>CTR: POST /validate step=N
CTR->>CTR: session.completedAt = now()
CTR->>DB: UPDATE pedagogique_sessions<br/>SET completed_at
CTR->>CTR: stars = computeStars(level,<br/>dur <= timer)
CTR-->>OVR: {valid, completed:true,<br/>stars:4, maxStars:5}
Note over E: Navigation /s/{token}
SUC->>CTR: GET /pedagogique/succes/{token}
CTR->>DB: SELECT session
CTR->>CTR: stars = computeStars(level, inTime)
CTR-->>SUC: {alias, stars:4, maxStars:5,<br/>durationSeconds:3842}
SUC->>SUC: SuccesStars render ⭐⭐⭐⭐☆
Note over ADM: Formateur consulte sessions
ADM->>CTR: GET /pedagogique/sessions
CTR->>CTR: Pour chaque session complétée :<br/>computeStars(level, dur<=timer)
CTR-->>ADM: [{alias, stars, maxStars, ...}]
Le score est calculé à trois endroits qui doivent rester cohérents :
validatePedagogique()— réponse à la validation de l'étape finale, directement vers l'overlay étudiant.buildPedagogiqueState()— inclus dans chaque poll/statuspour les étudiants dont la session est déjà complétée (persistance de l'affichage après reload).getSessions()etbuildSessionSnapshot()— pour la vue formateur en temps réel et pour l'archivage danslastSessionSummaryau moment de la désactivation.
Les trois appels utilisent la même méthode statique
computeStars(level, inTime) — garantissant un résultat identique
quel que soit le chemin de lecture.
Archivage dans le snapshot formateur¶
Quand le formateur clique sur Désactiver (ou démarre un nouveau
parcours), buildSessionSnapshot() construit un instantané immuable
de toutes les sessions pour la vue lastSessionSummary :
if (completed && s.completedAt > 0) {
long dur = (s.completedAt - s.joinedAt) / 1000L;
m.put("durationSeconds", dur);
m.put("stars", computeStars(pedagogiqueLevel, dur <= pedagogiqueTimer));
m.put("maxStars", computeStars(pedagogiqueLevel, true));
}
Le snapshot stocke à la fois les étoiles obtenues et les étoiles
maximales du niveau — ce qui permet à l'UI d'afficher correctement
4 / 5 ⭐ même après que le parcours a été arrêté et vidé de la base
par clearAll().
Rôle pédagogique du malus¶
Le malus d'une étoile pour dépassement est un signal doux, pas une
sanction. Un étudiant qui termine un bac5 en 2 heures au lieu de 1h30
repart avec 4 étoiles au lieu de 5 — il sait qu'il a terminé mais que
le rythme n'était pas optimal, ce qui alimente la discussion en débrief
de séance.
Ce choix est volontairement non configurable depuis l'UI formateur : personnaliser le barème en cours de session romprait la comparabilité entre cohortes. Modifier le comportement requiert une édition du code source et un nouveau build.
Champ maxStars dans les réponses API¶
Toutes les réponses de progression incluent à la fois stars (étoiles
obtenues) et maxStars (étoiles atteignables sur ce niveau). Cette
redondance facilite le rendu côté frontend — aucun composant n'a besoin
de connaître le barème en dur :
Le composant React SuccesStars lit ces deux valeurs et rend trois
étoiles pleines (⭐) suivies de zéro étoiles vides (☆) — ou, pour un
bac5 hors temps, quatre pleines suivies d'une vide (4/5).
Pages suivantes : ← Code agent · Indices → · Page de succès →