Chapitre 5.4 — Le rollup
⏱️ TL;DR — Un regroupement (racine, module) n’exécute aucun SCO : son état est calculé à partir de ses enfants. Ce calcul, c’est le rollup — la remontée d’état de bas en haut de l’arbre. Par défaut, un parent est « satisfait » quand tous ses enfants suivis le sont, et « complété » quand tous le sont ; on peut personnaliser avec des
<imsss:rollupRules>(par exemple « satisfait si au moins un enfant l’est ») et pondérer la contribution de chaque enfant à la mesure du parent viaobjectiveMeasureWeight. C’est ici que naît le bug le plus frustrant de SCORM 2004 : « mon cours reste incomplet alors que tout est fait » — presque toujours un enfant qui n’a jamais posécmi.completion_status = completed(resté àunknown), ou un rollup mal réglé. Ce chapitre t’apprend à le lire et à le réparer.
🎯 Objectifs
- Comprendre le rollup : la remontée de satisfaction et de complétion vers le parent.
- Connaître le comportement par défaut et savoir le personnaliser avec
<imsss:rollupRules>. - Pondérer la contribution d’un enfant à la mesure du parent avec
objectiveMeasureWeight. - Diagnostiquer « mon cours reste incomplet alors que tout est fait ».
Le principe : l’état monte, il ne descend pas
Dans l’arbre d’activités (5.2), seules les feuilles (les SCO) rapportent directement un état : elles posent completion_status, success_status, un score. Les regroupements n’ont pas de contenu ; leur état doit être déduit de leurs enfants. Cette déduction, effectuée par le moteur du LMS de bas en haut, est le rollup.
Les flèches montent : chaque SCO rapporte son état, chaque module agrège celui de ses enfants, la racine agrège celui des modules. Ici, MOD1 peut être complété (ses deux enfants le sont) mais MOD2 ne l’est pas (Chapitre 3 incomplete), donc la racine « Cours » reste incomplète — même si trois activités sur quatre sont finies. Le rollup ne connaît pas la nuance « presque » : par défaut, il faut tout.
Deux axes remontent : satisfaction ET complétion
Comme au chapitre 5.1, deux notions distinctes remontent séparément :
- La complétion du parent (est-il « complété » ?) agrège les
completion_statusdes enfants. - La satisfaction du parent (son objectif principal est-il satisfait ?) agrège les objectifs des enfants.
Le comportement par défaut (sans rollupRules explicites) est, en substance :
- le parent est complété si tous ses enfants suivis sont complétés ;
- le parent est satisfait si tous ses enfants suivis sont satisfaits.
Deux réglages contrôlent globalement cette remontée sur un nœud parent :
<imsss:sequencing>
<imsss:rollupRules rollupObjectiveSatisfied="true"
rollupProgressCompletion="true">
<!-- (règles personnalisées éventuelles ici) -->
</imsss:rollupRules>
</imsss:sequencing>rollupObjectiveSatisfied et rollupProgressCompletion (vrais par défaut) disent si la satisfaction / la complétion de ce nœud participe au rollup de son parent. Les passer à false débranche l’enfant du calcul du parent — un levier puissant… et une cause de bug quand c’est involontaire.
Personnaliser : rollupRules explicites
Le « tout ou rien » par défaut ne convient pas toujours. Exemple : un module de trois exercices au choix où en réussir un seul suffit. On écrit une règle de rollup :
<item identifier="ITEM-MOD-CHOIX">
<title>Module au choix</title>
<imsss:sequencing>
<imsss:rollupRules>
<!-- Satisfait si AU MOINS UN enfant est satisfait -->
<imsss:rollupRule childActivitySet="any">
<imsss:rollupConditions>
<imsss:rollupCondition condition="satisfied"/>
</imsss:rollupConditions>
<imsss:rollupAction action="satisfied"/>
</imsss:rollupRule>
<!-- Complété si AU MOINS UN enfant est complété -->
<imsss:rollupRule childActivitySet="any">
<imsss:rollupConditions>
<imsss:rollupCondition condition="completed"/>
</imsss:rollupConditions>
<imsss:rollupAction action="completed"/>
</imsss:rollupRule>
</imsss:rollupRules>
</imsss:sequencing>
</item>Le childActivitySet est le cœur de la règle : all (tous), any (au moins un), none, atLeastCount, atLeastPercent. La rollupAction dit ce qu’on affirme du parent (satisfied, notSatisfied, completed, incomplete) quand la condition est réunie sur l’ensemble d’enfants désigné.
💡 Réflexe — Avant d’écrire une
rollupRule, formule-la en français : « le parent est [satisfait/complété] si [tous / au moins un / au moins N] des enfants sont [satisfaits/complétés] ». Cette phrase se traduit directement : le sujet →rollupAction, le quantificateur →childActivitySet, le critère →rollupCondition. Si tu ne sais pas dire la phrase, tu ne sais pas encore ce que tu veux régler.
Pondérer la mesure : objectiveMeasureWeight
La mesure (le score normalisé) remonte aussi : le score d’un parent est une moyenne pondérée des mesures de ses enfants. Le poids de chaque enfant est objectiveMeasureWeight (défaut 1), posé sur le rollupRules de l’enfant.
Cas typique : un module contient un cours (non noté) et un test (noté). Tu ne veux pas que le cours, avec sa mesure vide ou nulle, tire la moyenne du module vers le bas. On met son poids à 0 :
<!-- L'activité "cours" ne compte PAS dans la mesure du parent -->
<item identifier="ITEM-COURS" identifierref="RES-COURS">
<title>Support de cours</title>
<imsss:sequencing>
<imsss:rollupRules objectiveMeasureWeight="0"/>
</imsss:sequencing>
</item>
<!-- Seul le "test" pèse dans la mesure du parent -->
<item identifier="ITEM-TEST" identifierref="RES-TEST">
<title>Test</title>
<imsss:sequencing>
<imsss:rollupRules objectiveMeasureWeight="1"/>
</imsss:sequencing>
</item>Avec ce réglage, la mesure du module est celle du test, sans dilution. Sans lui, le module afficherait une moyenne entre 0 (le cours non noté) et la note du test — un score artificiellement bas que le client prendra pour un bug.
⚠️ Piège — Laisser un
objectiveMeasureWeightpar défaut (1) sur des activités non notées. Leur mesure vide ou nulle entre dans la moyenne du parent et plombe son score. Symptôme : « le module affiche 45 % alors que l’apprenant a eu 90 % au seul test noté ». Mets le poids des activités non évaluées à0.
Le grand classique : « mon cours reste incomplet alors que tout est fait »
C’est le ticket récurrent du séquencement 2004. L’apprenant a tout parcouru, tout réussi, et pourtant le cours s’affiche « incomplet » ou « non satisfait ». Les causes, par ordre de fréquence :
- Un SCO qui ne pose jamais
cmi.completion_status = "completed". Il le laisse àunknown(valeur initiale). Le rollup « tous complétés » échoue à cause de ce seul enfant. La cause n°1 de loin. Vérifie que chaque SCO écrit explicitement sa complétion avantTerminate. - Un enfant débranché du rollup par
rollupProgressCompletion="false"(ourollupObjectiveSatisfied="false") laissé par erreur : il ne participe pas, mais un autre enfant, lui, reste incomplet. - Une activité non suivie / cachée qui compte quand même dans le « tous » du rollup par défaut.
- Confusion complétion / satisfaction : le cours attend la satisfaction (
success_status/mesure) alors que les SCO n’ont posé que la complétion — ou l’inverse. Les deux axes remontent séparément (5.1) ; régler l’un ne règle pas l’autre. - Un
objectiveMeasureWeightou un seuil mal placé qui empêche la mesure d’atteindre leminNormalizedMeasuredu parent.
💡 Réflexe — Débogue le rollup de bas en haut. Ne regarde pas d’abord le cours : inspecte chaque feuille. Pour chacune, note son
completion_statuset sonsuccess_statusréels en fin de session (via les logs du LMS ou SCORM Cloud). Neuf fois sur dix, tu trouves une feuille restée àunknown— le regroupement ne peut pas être « tous complétés » avec un enfant inconnu. Corrige la feuille, le rollup se répare tout seul.
🔌 Côté intégration — Quand un client te dit « ton module ne se complète pas dans notre LMS », la question n’est pas « ton LMS est buggé ? » mais « quelle feuille reste
unknown? ». Demande-lui l’export de suivi détaillé (état par SCO). S’il montre troiscompletedet ununknown, le coupable est ton contenu, pas sa plateforme — et tu corriges en posant explicitementcmi.completion_statusdans le SCO fautif. Savoir désigner la feuille coupable fait gagner des jours de ping-pong.
📚 La spec — Le rollup, ses
rollupRules,childActivitySet,rollupCondition/rollupAction,objectiveMeasureWeight,rollupObjectiveSatisfiedetrollupProgressCompletionsont définis par le livre SN de SCORM 2004 (fondé sur IMS Simple Sequencing). L’algorithme exact d’agrégation (l’Overall Rollup Process et ses sous-processus) est décrit en pseudo-code dans le SN — la référence pour comprendre un comportement d’agrégation précis, sur adlnet.gov .
🧭 Sur FormaCampus — Sur le cours « Sécurité au travail », FormaCampus rencontre pile ce bug : le tableau de bord montre des apprenants « incomplets » alors qu’ils ont tout validé. En déboguant feuille par feuille, l’équipe trouve le coupable : le SCO « Introduction » n’écrivait jamais
cmi.completion_status— il se contentait deTerminate, laissant la valeur àunknown. Une seule ligne (SetValue("cmi.completion_status", "completed")) répare tout le rollup. La leçon interne : chaque SCO doit poser explicitement sa complétion, et l’équipe l’ajoute à sa checklist de conformité avant tout export.
✏️ Exercices
Exercice 1 — Le cours reste incomplet. Un module de 4 SCO : trois remontent completion_status = "completed", le quatrième unknown. Tous les success_status sont passed. Le module s’affiche « incomplet ». Pourquoi, et comment corriger ?
✅ Solution
Rollup de complétion par défaut = « tous les enfants complétés ». Le quatrième SCO est resté à unknown (il n’a jamais posé sa complétion) → la condition « tous complétés » échoue → le module reste incomplet, indépendamment des success_status (qui, eux, concernent la satisfaction, un autre axe).
Correction : faire écrire au 4ᵉ SCO cmi.completion_status = "completed" avant Terminate. C’est presque toujours un SCO qui oublie de poser sa complétion, pas un défaut du LMS.
Exercice 2 — Score plombé. Un module « Chapitre + Test » affiche une mesure de 0,45 alors que l’apprenant a eu 0,9 au test. Le chapitre n’est pas noté. Diagnostic et correctif ?
✅ Solution
La mesure du parent est une moyenne pondérée des enfants. Avec les poids par défaut (1 chacun), la mesure = moyenne de 0 (chapitre non noté) et 0.9 (test) ≈ 0.45. Le chapitre non noté dilue le score.
Correctif : poser objectiveMeasureWeight="0" sur le chapitre (via son <imsss:rollupRules objectiveMeasureWeight="0"/>). La mesure du parent devient alors celle du seul test noté : 0.9.
Exercice 3 — Rollup « au moins un ». Écris la rollupRule qui déclare un module satisfait dès qu’au moins un de ses enfants est satisfait (exercices au choix).
✅ Solution
<imsss:rollupRules>
<imsss:rollupRule childActivitySet="any">
<imsss:rollupConditions>
<imsss:rollupCondition condition="satisfied"/>
</imsss:rollupConditions>
<imsss:rollupAction action="satisfied"/>
</imsss:rollupRule>
</imsss:rollupRules>childActivitySet="any" = au moins un enfant ; condition="satisfied" = le critère ; action="satisfied" = ce qu’on affirme du parent. Traduction directe de « satisfait si au moins un enfant l’est ».
🧠 Quiz de révision
1. Qu’est-ce que le rollup ?
La remontée de l’état des activités enfants vers leur parent (regroupement) : un regroupement n’exécutant aucun SCO, sa complétion et sa satisfaction sont calculées à partir de ses enfants, de bas en haut de l’arbre.
2. Quel est le comportement de rollup par défaut ?
Le parent est complété si tous ses enfants suivis le sont, et satisfait si tous le sont. Le « tout ou rien » — personnalisable avec des rollupRules explicites (childActivitySet="any", etc.).
3. Cause n°1 de « mon cours reste incomplet alors que tout est fait » ?
Un SCO qui n’a jamais posé cmi.completion_status = "completed" (resté à unknown). Le rollup « tous complétés » échoue à cause de ce seul enfant. On débogue de bas en haut, feuille par feuille.
4. À quoi sert objectiveMeasureWeight ?
objectiveMeasureWeight ?À pondérer la contribution d’un enfant à la mesure (score normalisé) du parent. Mettre 0 sur une activité non notée l’empêche de diluer le score du module ; défaut 1.
5. La complétion et la satisfaction remontent-elles ensemble ?
Non, elles remontent séparément — deux axes distincts (comme au chapitre 5.1). Régler le rollup de complétion ne règle pas celui de satisfaction, et inversement.
Chapitre suivant : Pourquoi si peu utilisé — le bilan honnête : un séquencement puissant mais complexe, et quand il vaut (ou pas) le coup.