Skip to Content

Chapitre 4.4 — Statut, score & suspend_data

⏱️ TL;DR — Trois champs décident du sort de ton module. Le statut dit si l’apprenant a terminé (et réussi) : c’est le piège n°1 du e-learning — un SCO qui reste bloqué sur incomplete parce que le code ne pose jamais completed/passed. Le score doit être posé dans le bon champ et la bonne échelle : cmi.core.score.raw (0–100) en 1.2, cmi.score.scaled (-1 à 1) en 2004. Et suspend_data est la mémoire libre du SCO : tu y sérialises l’état pour permettre la reprise — mais avec une limite de taille (4096 caractères en 1.2, 64000 en 2004) qui impose un format compact. Complété par lesson_location/location (le signet). Rate l’un de ces trois, et c’est « ça marche à l’écran mais rien ne compte ».

🎯 Objectifs

  • Poser correctement le statut : lesson_status en 1.2, le couple completion_status + success_status en 2004.
  • Éviter le piège n°1 : le module qui reste incomplete faute de passer completed.
  • Choisir le bon champ et la bonne échelle de score (raw vs scaled), et gérer un masteryScore.
  • Utiliser suspend_data et lesson_location/location pour une reprise fiable, en restant sous la limite de taille.

Le piège n°1 : rester incomplete

Le scénario, on l’a vu sur FormaCampus : le module se déroule parfaitement, l’apprenant arrive au bout, ferme… et le LMS affiche toujours « en cours ». Neuf fois sur dix, la cause est le statut jamais mis à completed.

Le statut ne devient pas completed tout seul. Aucun LMS ne « devine » qu’un apprenant a fini parce qu’il a vu le dernier écran. C’est au SCO de le déclarer, explicitement, par un SetValue sur le bon champ, puis de committer et clore (chapitre 4.2).

En SCORM 1.2 : un seul champ, lesson_status

cmi.core.lesson_status porte tout. Son vocabulaire fermé :

ValeurSens
not attemptedJamais commencé.
incompleteCommencé, pas fini. État de départ typique.
browsedParcouru en mode consultation.
completedTerminé (sans notion de réussite).
passedTerminé et réussi (score au-dessus du seuil).
failedTerminé et échoué.

La bonne séquence : poser incomplete à l’ouverture, puis completed (ou passed/failed s’il y a un seuil) à la fin.

// SCORM 1.2 — le statut au bon moment api.LMSInitialize(""); api.LMSSetValue("cmi.core.lesson_status", "incomplete"); // départ api.LMSCommit(""); // ... plus tard, l'apprenant a fini le module (contenu sans note) ... api.LMSSetValue("cmi.core.lesson_status", "completed"); // LE geste qui manquait api.LMSCommit(""); api.LMSFinish("");

S’il y a un score seuil (masteryScore), on tranche passed/failed :

// Avec un seuil : le SCO calcule passed/failed const seuil = Number(api.LMSGetValue("cmi.core.student_data.mastery_score")) || 80; const note = 74; api.LMSSetValue("cmi.core.score.raw", String(note)); api.LMSSetValue( "cmi.core.lesson_status", note >= seuil ? "passed" : "failed" ); api.LMSCommit("");

⚠️ Piège — Laisser le statut à incomplete (ou pire, ne jamais l’écrire, donc not attempted). C’est la cause n°1 des tickets « le module ne valide pas ». Le contenu peut être irréprochable : sans SetValue("...completed"/"passed"), le LMS n’a aucune raison de valider. Le statut est un acte explicite, jamais un effet de bord.

En SCORM 2004 : deux champs séparés

2004 découple la complétion (a-t-on parcouru le contenu ?) de la réussite (a-t-on atteint le seuil ?). Il faut poser les deux — sinon l’un des deux reste unknown.

// SCORM 2004 — complétion ET réussite, séparément api.Initialize(""); api.SetValue("cmi.completion_status", "incomplete"); // départ api.SetValue("cmi.success_status", "unknown"); api.Commit(""); // ... à la fin, avec un score et un seuil de 0,8 (échelle scaled -1..1) ... const scaled = 0.74; api.SetValue("cmi.score.scaled", String(scaled)); api.SetValue("cmi.completion_status", "completed"); // parcouru api.SetValue("cmi.success_status", scaled >= 0.8 ? "passed" : "failed"); // réussi ? api.Commit(""); api.Terminate("");

Cette séparation permet des états impossibles en 1.2 : « terminé mais échoué » (completed + failed), ou « réussi sans être marqué terminé » — utile quand la complétion et la note obéissent à des règles distinctes.

💡 Réflexe — Décide à l’avance ce que « terminé » veut dire pour ton module (vu tous les écrans ? répondu au quiz ? atteint le seuil ?) et écris-le noir sur blanc dans le code, au bon moment. « Terminé » est une règle métier, pas une intuition. En 2004, n’oublie jamais de poser les deux statuts.

Le score : raw, scaled, et le seuil

AspectSCORM 1.2SCORM 2004
Champ principalcmi.core.score.raw (souvent 0–100)cmi.score.scaled (-1 à 1)
Bornescmi.core.score.min / .maxcmi.score.min / .max (+ raw)
Seuil de réussitecmi.core.student_data.mastery_scoremasteryScore (manifeste / séquencement)

Deux erreurs fréquentes :

  • Envoyer un raw sans min/max : un score « 42 » n’a de sens que si on sait qu’il est sur 50 ou sur 100. Pose min et max pour lever l’ambiguïté.
  • Confondre les échelles : en 2004, cmi.score.scaled est un ratio, pas un pourcentage entier. 85 % s’écrit "0.85", pas "85". Un "85" dans scaled est hors plage et sera rejeté.
// Bien poser un score en SCORM 2004 const note = 85, total = 100; api.SetValue("cmi.score.raw", String(note)); // "85" api.SetValue("cmi.score.min", "0"); api.SetValue("cmi.score.max", String(total)); // "100" api.SetValue("cmi.score.scaled", (note / total).toFixed(2)); // "0.85"

suspend_data : la mémoire libre du SCO

cmi.suspend_data (1.2 et 2004) est un champ libre : le LMS le stocke tel quel et te le rend à la reprise, sans jamais l’interpréter. C’est ta mémoire pour reconstruire l’état exact du module : réponses déjà données, écrans visités, progression fine, tirage aléatoire des questions… Tout ce dont tu as besoin pour que l’apprenant reprenne où il s’était arrêté.

Mais il y a une limite de taille stricte :

VersionTaille max de suspend_data
SCORM 1.24096 caractères
SCORM 200464000 caractères

4096 caractères, c’est très peu. Un JSON verbeux les explose en quelques dizaines de questions. La discipline : sérialiser compact.

// À NE PAS FAIRE en 1.2 : JSON verbeux, explose vite les 4096 caractères const gros = JSON.stringify({ pageCourante: 7, reponses: [ { question: "q1", choix: "b", correct: true }, { question: "q2", choix: "a", correct: false } // ... 40 questions et c'est mort ] });
// MIEUX : format positionnel ultra-compact (à toi de le documenter) // "7|10110..." = page 7, puis 1 bit par question (1 = correct) const page = 7; const bits = [1, 0, 1, 1, 0]; // résultats des questions const compact = page + "|" + bits.join(""); api.LMSSetValue("cmi.suspend_data", compact); api.LMSCommit("");

À la reprise, on relit et on reconstruit :

// Reprise : lire le contexte d'entrée, puis restaurer depuis suspend_data const entry = api.LMSGetValue("cmi.core.entry"); // "resume" si reprise if (entry === "resume") { const brut = api.LMSGetValue("cmi.suspend_data"); const [page, bits] = brut.split("|"); restaurerEtat(Number(page), bits.split("").map(Number)); }

⚠️ Piège — Dépasser les 4096 caractères en SCORM 1.2. Selon le LMS, la valeur est tronquée (état corrompu à la reprise) ou rejetée (rien n’est sauvé). Symptôme : « la reprise remet l’apprenant au début » ou « plante après X questions ». Toujours mesurer suspend_data.length avant d’écrire, et prévoir un format qui tient dans le budget.

lesson_location : le signet

cmi.core.lesson_location (1.2) / cmi.location (2004) est le signet court : un repère de reprendre (numéro d’écran, identifiant de section). À distinguer de suspend_data :

  • lesson_location = « j’en étais » (court : "ecran-7").
  • suspend_data = « dans quel état j’étais » (riche : réponses, progression…).
// À chaque changement d'écran : mettre à jour le signet api.LMSSetValue("cmi.core.lesson_location", "ecran-7"); api.LMSSetValue("cmi.core.exit", "suspend"); // "je pars mais je reprendrai" api.LMSCommit("");

Le cmi.core.exit à suspend (1.2) / cmi.exit à suspend (2004) signale au LMS que la sortie est une mise en pause : c’est ce qui fait que cmi.core.entry vaudra resume à la prochaine ouverture, déclenchant ta logique de reprise.

🧭 Sur FormaCampus — Diagnostic bouclé. Le module fautif avait deux bugs : (1) LMSFinish jamais appelé en fermeture d’onglet (chapitre 4.2), et (2) cmi.core.lesson_status jamais posé à completed — le code se contentait de incomplete à l’ouverture et n’écrivait rien à la fin, parce que la « fin » était un écran de félicitations sans appel d’API. Correction en deux gestes : poser cmi.core.lesson_status à completed (ou passed si la note atteint le masteryScore) au moment où l’apprenant valide le dernier écran, puis LMSCommit + LMSFinish garantis via pagehide. Résultat côté Moodle de l’école : la complétion remonte enfin, les stagiaires passent en « terminé », l’enseignant voit les scores. Un ticket client fermé grâce à trois SetValue bien placés.

📚 La spec — Les vocabulaires fermés (lesson_status, completion_status, success_status), les plages de score (scaled de -1 à 1) et les tailles maximales de suspend_data (4096 en 1.2, 64000 en 2004) sont fixés dans le livre RTE de chaque version SCORM (ADL, adlnet.gov). Le masteryScore et la logique de « satisfaction » relèvent aussi du CAM (manifeste) et, en 2004, du séquencement — voir Partie 5.

✏️ Exercices

Exercice 1 — Pourquoi ça ne valide pas ? Un SCO 1.2 fait exactement ceci et l’école se plaint que « rien ne passe en terminé » :

api.LMSInitialize(""); api.LMSSetValue("cmi.core.lesson_status", "incomplete"); // ... l'apprenant parcourt tout et arrive à l'écran final ... api.LMSCommit(""); api.LMSFinish("");

Diagnostique et corrige.

✅ Solution

Le statut est posé à incomplete à l’ouverture et jamais remis à completed/passed à la fin. Le Commit/Finish sont bien là, mais ils persistent un statut… incomplete. Le LMS fait exactement ce qu’on lui dit : « en cours ». C’est le piège n°1.

Correction : écrire le statut final avant de clore.

api.LMSInitialize(""); api.LMSSetValue("cmi.core.lesson_status", "incomplete"); // ... à l'écran final, l'apprenant valide ... api.LMSSetValue("cmi.core.lesson_status", "completed"); // le geste manquant api.LMSSetValue("cmi.core.exit", ""); // sortie normale api.LMSCommit(""); api.LMSFinish("");

Exercice 2 — Budget suspend_data. En SCORM 1.2, tu dois mémoriser, pour reprise, la page courante et le résultat (correct/incorrect) de 300 questions. Un JSON.stringify d’objets {question, correct} fait ~9000 caractères. Propose un format qui tient dans 4096.

✅ Solution

Un objet par question est ~30× trop gros. Un format positionnel à 1 caractère par question suffit : l’indice encode la question, le caractère encode le résultat.

// "42#1011001..." : page 42, puis 300 caractères (1 = correct, 0 = incorrect) const page = 42; const bits = resultats.map((r) => (r ? "1" : "0")).join(""); // 300 caractères const data = page + "#" + bits; // ~304 caractères, très loin des 4096 api.LMSSetValue("cmi.suspend_data", data);

300 caractères pour 300 questions, contre 9000 en JSON. On peut compresser encore (bitmask base64) si besoin, mais l’idée clé est : 1 question = 1 caractère, et on documente le format. Toujours vérifier data.length < 4096 avant d’écrire.

Exercice 3 — 1.2 vs 2004 : le statut. Un apprenant a tout parcouru mais a échoué au test final (score sous le seuil). Écris le statut correct en 1.2 et en 2004.

✅ Solution

SCORM 1.2 — un seul champ ; « terminé mais échoué » se dit failed (qui implique la complétion) :

api.LMSSetValue("cmi.core.lesson_status", "failed");

SCORM 2004 — on sépare : parcouru et échoué :

api.SetValue("cmi.completion_status", "completed"); // il a tout parcouru api.SetValue("cmi.success_status", "failed"); // mais raté le seuil

C’est précisément le cas que 2004 exprime mieux que 1.2 : en 1.2, failed mélange les deux idées ; en 2004, on distingue « a fini » de « a réussi ».

🧠 Quiz de révision

1. Quel est le piège n°1 du runtime SCORM ?

Le statut qui reste incomplete (ou not attempted) parce que le code ne pose jamais completed/passed à la fin. Le LMS ne devine pas la complétion : c’est un SetValue explicite, suivi de Commit/Terminate.

2. En SCORM 2004, combien de champs de statut faut-il poser, et lesquels ?

Deux : cmi.completion_status (completed/incomplete/…) pour la complétion, et cmi.success_status (passed/failed/unknown) pour la réussite. Oublier l’un laisse l’autre à unknown.

3. Comment s’écrit 85 % dans cmi.score.scaled ?

"0.85". cmi.score.scaled est un ratio de -1 à 1, pas un pourcentage entier. "85" serait hors plage et rejeté. (En 1.2, cmi.core.score.raw vaut bien "85" sur 100.)

4. Quelle est la taille maximale de suspend_data ?

4096 caractères en SCORM 1.2, 64000 en SCORM 2004. En 1.2 surtout, il faut sérialiser compact (format positionnel, 1 caractère par item) et vérifier la longueur avant d’écrire.

5. Quelle différence entre lesson_location et suspend_data ?

lesson_location/location = reprendre (signet court, ex. "ecran-7"). suspend_data = dans quel état reprendre (données riches : réponses, progression). On combine les deux, avec exit=suspend pour que la prochaine entry vaille resume.


Chapitre suivant : 4.5 — Débuguer le runtime — quand malgré tout « rien ne remonte » : lire les erreurs, dérouler la checklist, isoler le SCO muet.

Last updated on