Skip to Content
Interop EdTechPartie 10 — LTI Advantage10.3 — AGS : renvoyer des notes

Chapitre 10.3 — AGS : renvoyer des notes

⏱️ TL;DRAGS (Assignment and Grade Services) est le service qui crée la valeur : ton outil écrit dans le carnet de notes du LMS. Le lancement te livre l’endpoint dans le claim .../lti-ags/claim/endpoint = { "scope": [...], "lineitems": "URL", "lineitem": "URL" }. Une colonne de carnet s’appelle un lineitem ; on les gère via le conteneur lineitems (GET/POST). Pour publier une note, on fait POST {lineitem}/scores avec un objet Score (userId, scoreGiven, scoreMaximum, activityProgress, gradingProgress, timestamp, comment). Pour relire les notes, GET {lineitem}/results. Trois scopes : .../lti-ags/scope/lineitem (gérer les colonnes), .../lti-ags/scope/score (publier), .../lti-ags/scope/result.readonly (lire). Ce chapitre te fait remonter une note de bout en bout.

🎯 Objectifs

  • Lire le claim endpoint AGS et distinguer lineitems (conteneur) de lineitem (colonne).
  • Publier un score : POST {lineitem}/scores avec un objet Score complet et correct.
  • Créer une colonne (lineitem) quand elle n’existe pas encore.
  • Relire les résultats via GET {lineitem}/results.
  • Comprendre activityProgress / gradingProgress et pourquoi ils comptent.

Le claim endpoint AGS

Tout part du claim déjà croisé au chapitre 10.1, présent dans le id_token du lancement :

{ "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint": { "scope": [ "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", "https://purl.imsglobal.org/spec/lti-ags/scope/score" ], "lineitems": "https://moodle.ecole.fr/mod/lti/services.php/2/lineitems", "lineitem": "https://moodle.ecole.fr/mod/lti/services.php/2/lineitems/5/lineitem" } }

Deux URLs, deux granularités :

  • lineitems (pluriel) : le conteneur de toutes les colonnes du cours. GET pour lister, POST pour créer une colonne.
  • lineitem (singulier) : la colonne déjà liée à cette activité précise. Présente quand l’activité a été créée avec une colonne (souvent via le lineItem du Deep Linking, chapitre 10.2). C’est sur elle que tu postes le score.

Le champ scope liste ce que cette plateforme t’autorise. Tu demanderas ces scopes lors de l’obtention du jeton d’accès.

Cas nominal : publier un score

Ton élève finit l’exercice avec 17/20. Tu as déjà l’URL lineitem (persistée au lancement) et un access_token de scope .../lti-ags/scope/score (chapitre 10.1). Tu postes sur {lineitem}/scores :

POST /mod/lti/services.php/2/lineitems/5/lineitem/scores HTTP/1.1 Host: moodle.ecole.fr Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9... Content-Type: application/vnd.ims.lis.v1.score+json { "userId": "u-2342", "scoreGiven": 17, "scoreMaximum": 20, "activityProgress": "Completed", "gradingProgress": "FullyGraded", "timestamp": "2026-05-12T14:32:05.000Z", "comment": "Bravo. Revois les fractions equivalentes (question 7)." }

Champ par champ :

  • userId : l’identifiant de l’élève chez la plateforme — c’est le sub du lancement (chapitre 9.5), pas l’e-mail. C’est la clé qui range la note en face du bon élève.
  • scoreGiven : la note obtenue. Si tu l’envoies, scoreMaximum devient obligatoire.
  • scoreMaximum : le barème (ici 20). La plateforme normalise scoreGiven sur ce maximum ; ne l’omets jamais quand tu envoies un scoreGiven.
  • activityProgress : où en est l’élève dans l’activité — valeurs Initialized, Started, InProgress, Submitted, Completed.
  • gradingProgress : où en est la correction — valeurs NotReady, Failed, Pending, PendingManual, FullyGraded.
  • timestamp : quand l’événement s’est produit, au format ISO 8601. Il ordonne les scores : un score plus récent l’emporte ; un timestamp antérieur au dernier reçu peut être ignoré.
  • comment : un retour textuel, affiché à l’élève dans le carnet (optionnel).

⚠️ Piège — Publier un scoreGiven sans scoreMaximum. La note remonte alors « brute » et la plateforme ne sait pas la rapporter au barème : 17 sur… combien ? Résultat courant : une note incohérente (17/100, ou rejet). Règle : scoreGiven et scoreMaximum vont toujours ensemble.

⚠️ Piège — Confondre activityProgress et gradingProgress. Le premier décrit l’élève (a-t-il fini l’activité ?), le second décrit la note (est-elle définitive ?). Pour qu’une note s’affiche comme acquise dans le carnet, il faut typiquement gradingProgress = FullyGraded. Si ta correction est différée (relecture d’un prof), envoie d’abord gradingProgress = PendingManual, puis un second score FullyGraded une fois corrigé.

💡 Réflexe — Rends l’envoi idempotent-friendly : un même exercice re-soumis produit un nouveau score avec un timestamp plus récent, et la plateforme garde le dernier. Ne cherche pas à « écraser » manuellement ; laisse le timestamp faire l’arbitrage. Et journalise chaque envoi (élève, colonne, score, réponse HTTP) : quand un prof dit « la note n’est pas remontée », ton log tranche en trois secondes.

La plateforme répond généralement par un 200 OK (ou 201/202 selon les implémentations). Le score apparaît dans le carnet, en face de l’élève, dans la colonne de l’activité.

Créer une colonne (lineitem)

Parfois, aucune colonne n’est encore liée (pas de Deep Linking, activité créée « nue »). Tu la crées toi-même en POST sur le conteneur lineitems, avec le scope .../lti-ags/scope/lineitem :

POST /mod/lti/services.php/2/lineitems HTTP/1.1 Host: moodle.ecole.fr Authorization: Bearer eyJ0eXAi... Content-Type: application/vnd.ims.lis.v2.lineitem+json { "scoreMaximum": 20, "label": "Calcul mental - fractions", "resourceId": "ex-4815", "resourceLinkId": "res-4815", "tag": "exercice" }
  • scoreMaximum et label sont requis (le barème et le nom de la colonne).
  • resourceId : ta référence interne (l’exercice ex-4815).
  • resourceLinkId : relie la colonne à l’activité (le resource_link.id du lancement) — pratique pour retrouver « la colonne de cette activité ».
  • tag : une étiquette libre pour catégoriser (filtrer tes colonnes ensuite).

La réponse renvoie la colonne créée avec son id (une URL) : c’est ce id que tu utiliseras comme {lineitem} pour poster les scores.

💡 Réflexe — Avant de créer, cherche : GET {lineitems}?resource_link_id=res-4815 (ou filtre sur tag/resource_id) pour voir si la colonne existe déjà. Créer aveuglément = colonnes en double dans le carnet, cauchemar de l’enseignant. Règle : trouver, sinon créer.

Relire les résultats

Pour lire les notes d’une colonne (afficher un suivi, recalculer une moyenne), GET sur {lineitem}/results, avec le scope .../lti-ags/scope/result.readonly :

GET /mod/lti/services.php/2/lineitems/5/lineitem/results HTTP/1.1 Host: moodle.ecole.fr Authorization: Bearer eyJ0eXAi... Accept: application/vnd.ims.lis.v2.resultcontainer+json

Réponse (un conteneur de résultats, un par élève noté) :

[ { "id": "https://moodle.ecole.fr/mod/lti/services.php/2/lineitems/5/results/u-2342", "userId": "u-2342", "resultScore": 17, "resultMaximum": 20, "scoreOf": "https://moodle.ecole.fr/mod/lti/services.php/2/lineitems/5/lineitem", "comment": "Bravo. Revois les fractions equivalentes (question 7)." } ]

Chaque résultat porte userId, resultScore, resultMaximum, scoreOf (la colonne concernée) et l’éventuel comment. Note l’asymétrie : tu écris des Score (scoreGiven), tu lis des Result (resultScore). Ce ne sont pas les mêmes objets — le Score est un événement que tu déposes, le Result est l’état courant que la plateforme calcule.

⚠️ Piège — Vouloir modifier une note en PUT sur un result. Les results sont en lecture seule (result.readonly). Pour changer une note, tu ne « modifies » pas le result : tu publies un nouveau Score (avec un timestamp plus récent). La plateforme recalcule le result. Un seul chemin d’écriture : scores.

Le parcours complet, résumé

ÉtapeVerbe HTTPURLScopeCorps / réponse
Créer une colonnePOST{lineitems}.../scope/lineitemobjet Line Item
Lister les colonnesGET{lineitems}.../scope/lineitemconteneur de Line Items
Publier une notePOST{lineitem}/scores.../scope/scoreobjet Score
Lire les notesGET{lineitem}/results.../scope/result.readonlyconteneur de Results

🔌 Côté intégration — Le renvoi de note est le critère qui fait passer ton outil de « ressource sympa » à « brique pédagogique ». Un enseignant n’adopte durablement qu’un outil dont les notes atterrissent dans son carnet, à côté des autres, sans double saisie. C’est aussi le service AGS que la certification 1EdTech vérifie le plus scrupuleusement (barème respecté, gradingProgress correct, idempotence par timestamp). Rate l’AGS, et tu restes hors des appels d’offres d’établissement.

🧭 Sur FormaCampus — Camille (élève de 6e A) termine « Calcul mental — fractions » avec 17/20 à 22 h 03. Un job du studio récupère l’endpoint AGS persisté au lancement, obtient un access_token (scope score), et POST un Score (userId = le sub de Camille, scoreGiven: 17, scoreMaximum: 20, activityProgress: "Completed", gradingProgress: "FullyGraded", timestamp). À 22 h 04, la note s’affiche dans le carnet Moodle de l’enseignant, dans la colonne créée au chapitre 10.2. Zéro ressaisie : le studio a écrit dans le carnet. C’est le moment où l’outil « vaut » un déploiement d’académie.

📚 La spec — AGS est spécifié par LTI Assignment and Grade Services (1EdTech). Le claim .../lti-ags/claim/endpoint, l’objet Score (userId, scoreGiven, scoreMaximum, activityProgress, gradingProgress, timestamp, comment), l’objet Line Item et le conteneur Result ont des schémas et des types de média précis (application/vnd.ims.lis.v1.score+json, .../v2.lineitem+json, .../v2.resultcontainer+json). Valeurs exactes de activityProgress/gradingProgress et détails sur 1edtech.org.

✏️ Exercices

Exercice 1 — Écris le Score. Un élève (sub = u-8890) obtient 8 sur 10 à un quiz entièrement auto-corrigé, terminé le 3 juin 2026 à 9 h 15 UTC. Écris l’objet Score à poster sur {lineitem}/scores.

✅ Solution

{ "userId": "u-8890", "scoreGiven": 8, "scoreMaximum": 10, "activityProgress": "Completed", "gradingProgress": "FullyGraded", "timestamp": "2026-06-03T09:15:00.000Z" }

userId = le sub. scoreGiven et scoreMaximum ensemble (8 sur 10). Activité terminée → Completed ; correction automatique et définitive → FullyGraded. timestamp en ISO 8601. Le comment est facultatif.

Exercice 2 — Diagnostique. Un enseignant se plaint : « L’exercice affiche 17/20 chez l’élève, mais mon carnet Moodle montre 17/100. » Cause probable et correction ?

✅ Solution

Le Score a été posté avec scoreGiven: 17 sans scoreMaximum (ou avec un scoreMaximum erroné). La plateforme a alors rapporté la note au barème de sa colonne (ici 100). Correction : toujours envoyer scoreMaximum: 20 avec le scoreGiven, et vérifier que le lineitem (ou son scoreMaximum à la création) correspond bien au barème de l’exercice. Republier un Score correct (avec un timestamp plus récent) remet la note d’équerre.

Exercice 3 — Trouver ou créer. Ton outil est lancé sur une activité sans colonne liée (lineitem absent du claim, seul lineitems est présent). Décris la marche à suivre avant de poster une note.

✅ Solution

  1. Obtenir un access_token avec le scope .../lti-ags/scope/lineitem. 2. Chercher une colonne existante : GET {lineitems} filtré (par resource_link_id ou tag). 3. Si elle existe, réutiliser son id. 4. Sinon, la créer : POST {lineitems} avec scoreMaximum, label (et resourceLinkId/resourceId), récupérer le id renvoyé. 5. Poster le Score sur {cet id}/scores (avec un jeton de scope score). Trouver, sinon créer — jamais créer à l’aveugle.

🧠 Quiz de révision

1. Quelle est la différence entre lineitems et lineitem ?

lineitems (pluriel) est le conteneur de toutes les colonnes du cours (GET pour lister, POST pour créer). lineitem (singulier) est une colonne, souvent celle liée à l’activité lancée ; c’est sur {lineitem}/scores qu’on poste les notes.

2. Sur quelle URL, avec quel scope, publie-t-on une note ?

POST {lineitem}/scores, avec le scope https://purl.imsglobal.org/spec/lti-ags/scope/score et le type de média application/vnd.ims.lis.v1.score+json.

3. Quels champs sont indispensables dans un Score ?

userId (le sub de l’élève), scoreGiven avec scoreMaximum, activityProgress, gradingProgress et timestamp. Le comment est optionnel.

4. Différence entre activityProgress et gradingProgress ?

activityProgress décrit l’avancement de l’élève dans l’activité (InitializedCompleted). gradingProgress décrit l’état de la correction (NotReadyFullyGraded). Pour une note définitive affichée, on vise gradingProgress: "FullyGraded".

5. Comment corrige-t-on une note déjà envoyée ?

On ne modifie pas les results (lecture seule). On publie un nouveau Score sur {lineitem}/scores avec un timestamp plus récent ; la plateforme recalcule le result et garde le plus récent.


Chapitre suivant : NRPS : lire le roster — connaître la classe sans que l’enseignant ressaisisse un seul nom.

Last updated on