MONITORING / METRICS

Metrics, progress and analytics

P2LRN separates portable content from tracking data. The Player displays status, scores, events and progression; The integrating platform decides what data to persist and how to relate it to users, intents, reports and analytics systems.

1. Tracking data sources

During playback, P2LRN exposes four types of information for different purposes: resume status, scores, events, and progression.

Status

loadState and saveState They serve to resume an activity exactly where it was. It is a replaceable snapshot, not a history.

Score

reportScore provides a normalized score when the activity has a usable grade.

Events

emitEvent receives the chronological flow p2lrn:*. It is the right source for analytics, auditing and time series.

Progress

The Player maintains a separate state with viewed, completed, passed and scorePercent to apply course rules.

Metrics are obtained through the Player's public contracts.

Host Adapter and events p2lrn:* are the interfaces intended for tracking and persistence. The Player's visible HTML and the internal content of SCORM/H5P iframes are part of the presentation, not the data contract.

2. Integration architecture

The Player remains independent of the host database. The integrating application provides the identity of the user and the attempt; Host Adapter matches that private data with the portable identifiers exposed by P2LRN.

User/attempt+context P2LRN→Host Adapter→Platform API→state / grades / events / LRS

Identity, registration, group, call and attempt belong to the integrating platform. They are not stored within the .p2lrn and P2LRN does not impose its data model.

3. Context and identification of activities

He context The Player uses package and graph identifiers. Combining them allows each tracking data to be associated with the exact version of the content without introducing private LMS identifiers into the package.

packageId

Logical identity of the package.

contentRoot

Content integrity footprint. It changes when content changes and avoids accidentally restoring state over another revision.

entityId

Activity, unit or root to which the data belongs.

resourceId

Resource associated with the activity when it exists.

representationId

Concrete representation that the Player is executing.

rendererId

Presentation/runtime chosen for that representation.

profile / runtime

Some system states, such as progression, identify their surface using these fields.

A robust persistence key typically combines learnerId and attemptId of the platform with packageId, contentRoot, entityId and the surface (rendererId, profile or runtime).

4. Integration via Host Adapter

The following example separates the private platform data from the P2LRN portable contract. currentUser, currentAttempt and learningApi belong to the integrating application; The Player only invokes the functions defined by the Host Adapter.

JavaScript
const session = Object.freeze({
  learnerId: currentUser.id,
  attemptId: currentAttempt.id,
});

function metricKey(context) {
  return {
    learnerId: session.learnerId,
    attemptId: session.attemptId,
    packageId: context.packageId,
    contentRoot: context.contentRoot,
    entityId: context.entityId ?? null,
    surface:
      context.profile ??
      context.rendererId ??
      context.runtime ??
      'package',
  };
}

const adapter = P2LRN.createHostAdapter({
  async loadState(context) {
    return learningApi.loadState(metricKey(context));
  },

  async saveState(state, context) {
    await learningApi.saveState(metricKey(context), state);
  },

  async reportScore(score, context) {
    await learningApi.saveScore(metricKey(context), score);
  },

  async emitEvent(event, context) {
    await learningApi.appendEvent({
      ...metricKey(context),
      event,
    });
  },
});

document.querySelector('p2lrn-player').adapter = adapter;
Host Adapter runs in the trusted context of the application.

Tokens, private endpoints, and user identity should remain outside the package. The platform API must authenticate and authorize each persistence operation.

5. Callback contract

loadState(context)

Called when opening an activity or progression. Returns the last supported snapshot or null. It is not an analytical event.

saveState(state, context)

Receives the complete status that allows you to resume. You must replace the previous snapshot of that same logical key.

reportScore(score, context)

Receive a structured score. It can be invoked more than once in contents that make successive commits; the “last”, “best” or “per attempt” policy belongs to the integrating platform.

emitEvent(event, context)

Receive the canonical envelope of the event. The calls are serialized in order by Adapter; It is the recommended way for an analytical log.

6. Canonical structure of events

All persistable events share the same envelope. He payload changes depending on the type of event and context allows it to be attributed to the corresponding activity.

JSON
{
  "schema": "https://p2lrn.org/schema/runtime-event/1",
  "version": "1",
  "type": "p2lrn:score",
  "timestamp": "2026-10-16T08:30:00.000Z",
  "context": {
    "packageId": "urn:uuid:...",
    "contentRoot": "sha256:...",
    "entityId": "urn:uuid:...",
    "resourceId": "urn:uuid:...",
    "representationId": "urn:uuid:...",
    "rendererId": "https://p2lrn.org/renderer/questionnaire/standard"
  },
  "payload": {
    "graded": true,
    "raw": 8,
    "max": 10,
    "normalized": 0.8
  }
}

In DOM listeners, the payload fields also appear directly in event.detail and the full wrap is available in event.detail.event. In HostAdapter.emitEvent() the canonical event is received directly.

7. Data available in quizzes and games

Quizzes and quiz-based games share the same session. The contract allows you to obtain answers, progress, time, attempts, final score and detail per question without depending on the visible interface.

p2lrn:answer

Includes questionId and the normalized answer to that question.

p2lrn:progress

Includes value from 0 to 1, answered and total. It is progress within the quiz, not a percentage of the completed course.

p2lrn:statechange

Snapshot with answers, current question, number of attempts completed, saved score, game status and timing.

p2lrn:timeupdate

Displays remaining time for the attempt and question when time limits exist.

p2lrn:timeout

Indicates whether the entire attempt or a specific question timed out.

p2lrn:score

Provide total grade and breakdown by question.

p2lrn:complete

Provide score, final answers and reason for ending.

Native punctuation has this format. normalized goes from 0 to 1 when the correction is automatic; if the correction mode belongs to the host, graded es false and normalized es null.

JSON · score
{
  "graded": true,
  "raw": 8,
  "max": 10,
  "normalized": 0.8,
  "questions": [
    {
      "questionId": "q1",
      "graded": true,
      "correct": true,
      "hits": 1,
      "errors": 0,
      "pointsAwarded": 2,
      "pointsPossible": 2
    }
  ]
}

The details of successes, errors, points obtained and unanswered questions are obtained through score.questions and the status of responses. The visible interface should not be used as a source to reconstruct this data.

8. Monitoring data in SCORM 1.2 and SCORM 2004

SCORM maintains its CMI model. P2LRN preserves that state and delivers it via saveState and p2lrn:statechange. The object contains state.values; the exact keys depend on the version and what the SCO actually writes.

SCORM 1.2

Note: cmi.core.score.raw, max, min. Status: cmi.core.lesson_status. Position: cmi.core.lesson_location. Tiempo: cmi.core.session_time and cmi.core.total_time.

SCORM 2004

Note: cmi.score.raw, max, min, scaled. Status: cmi.completion_status and cmi.success_status. Progreso: cmi.progress_measure. Position: cmi.location. Tiempo: cmi.session_time and cmi.total_time.

Interactions

cmi.interactions.* may contain id, type, learner response, result, latency, correct answers and objectives when published by the SCO.

Objectives

cmi.objectives.* preserves objectives and their notes/states when the package uses them.

P2LRN deriva reportScore when doing Commit or Terminate if there is a usable note and it has changed. For SCORM 2004 use first cmi.score.scaled; if it does not exist, you can normalize raw / max. The field passed comes out of cmi.success_status in 2004 or cmi.core.lesson_status in 1.2.

Data availability depends on each SCO.

Supporting a CMI key in runtime does not imply that all packages write it. The state should be kept as received and each field should be treated as optional.

9. Tracking data in H5P and xAPI

H5P communicates status for resume and can also issue xAPI. P2LRN forwards each statement as p2lrn:xapi and, when it contains a result, it derives a common score so that the integrating platform can obtain the basic score without interpreting each type of H5P content.

p2lrn:statechange

H5P status obtained from runtime to resume activity.

p2lrn:xapi

Includes the original statement in payload.statement, in addition to runtime:"h5p", source:"content-runtime" and authoritative:false.

p2lrn:score

Derives raw, max, normalized, passed and completed from statement.result when they are available.

reportScore

Called for xAPI terminal with verbs completed, passed or failed. The score is still marked as non-authoritative.

p2lrn:complete

It is emitted when receiving one of those terminal verbs and includes the statement and the derived punctuation.

The xAPI statements generated by the content do not represent trusted identity.

Before sending them to an LRS, the platform server must rebuild actor, authority, registration/attempt and any other data controlled by the LMS.

10. Course Progression Status

The progression uses a state separate from the internal states of each activity. The Player recalculates it from rendered, completion and scoring, saves it using the Host Adapter and outputs p2lrn:progressionchange when it changes.

JSON · p2lrn-progression-state/1
{
  "version": "p2lrn-progression-state/1",
  "entities": {
    "urn:uuid:activity-1": {
      "viewed": true,
      "completed": true,
      "passed": true,
      "scorePercent": 82.5,
      "updatedAt": "2026-10-16T08:30:00.000Z"
    }
  }
}

p2lrn:rendered you can mark an activity as viewed; p2lrn:complete the completed mark; the scores provide passed and scorePercent. Then they apply required, gateFollowing, completionRequirement, minScore and unlockWhen.

p2lrn:progress represents internal progress of an activity.

The overall progress of the course is obtained from the progression status and the rules declared by its entities. For example, a questionnaire can use p2lrn:progress to indicate how many questions have been answered.

11. Persistence model

Persistence typically separates the resume snapshot from the analytic history. The physical scheme belongs to the integrating platform; Separation by responsibility avoids mixing data with different life cycles.

Current status

One record per learner/attempt/activity/surface. It is replaced with each saveState.

Scores

Each report can be kept per attempt or subject to an explicit last or best grade policy. P2LRN does not define that policy.

Events

Optional log append-only with the canonical envelope, plus a receivedAt of the server.

Tried

Entity of the integrating platform with start, closure, registration, user and business rules. It is not part of the package.

To safely resume, the key must include contentRoot. If the content changes, the platform can migrate the state or initiate a compatible attempt instead of silently applying a snapshot of another version.

12. High frequency event management

High frequency events should not automatically become a persistent write for each change. p2lrn:timeupdate, responses and successive commits can be grouped according to analytics requirements.

  • saveState preserves the last resume snapshot and does not replace an event history.
  • Temporal analytics can be persisted asynchronously or in batches from emitEvent.
  • The event includes timestamp; For auditing, it is also advisable to add a receivedAt generated by the server.
  • The business session time should also be measured from the opening and closing of the attempt on the server, without depending solely on the browser's clock.
  • CMI times published by SCO can be maintained in SCORM; in timed quizzes, the timing status; in H5P, xAPI durations should be treated as content-generated data.

13. Retries, duplicates, and idempotency

emitEvent It is delivered in order within a single Adapter, but the P2LRN event does not define a durable database identifier. When the transport can retry requests, the server must use its own ingestion key.

A common strategy is to generate an identifier upon receiving the event and, when deterministic deduplication is required, combine intent + type + timestamp + context + payload hash. The exact policy depends on whether analytics should preserve legitimate replays.

14. Common integration cases

Nota final

reportScore provides raw, max, normalized and, when they exist, passed/completed.

Resumption

loadState + saveState They allow you to keep the snapshot by attempt and activity.

Analytics by question

The questionnaires expose p2lrn:answer, score.questions and complete.answers.

Interacciones SCORM

The keys cmi.interactions.* The saved state contains any interactions that the package has published.

xAPI H5P

p2lrn:xapi preserves the statement as content input; identity and authority They must be rebuilt on the server before being sent to the LRS.

Unlock status

p2lrn-progression-state/1 preserves the state upon which the Player applies progression rules.

15. Privacy and trust limits

The P2LRN package is portable and should not contain the learner's personal history. Host Adapter belongs to the trusted context of the host. Content events can describe the activity performed, but identity, registration, intent, permissions and final authority belong to the integrating platform.

The reference of Events documents the contract for each event; Progress describes the termination rules and Integration explains the connection with the platform services.

Rich pedagogical analytics

In addition to status, grade and completion, the native runtime allows you to observe the pedagogical journey with semantic events that do not depend on the visible HTML.

Intent Cycle

p2lrn:attempt differentiates a new attempt, a resumption and a final delivery. This avoids inferring sessions from browser times or reloads.

Response changes

p2lrn:interaction with action:"answer-change" allows you to measure reconsideration and navigation by question without duplicating the raw response in the analytical event.

Collaboration

Voting, note posting/deletion, contribution, and peer review generate semantic interactions within the same event contract.

Resources and means

Relevant opens and interactions can be incorporated into the history with their activity/resource context, keeping the presentation separate from the analytics.

The platform can combine these events with contentRoot, entityId, intent, and host user to build engagement, resumption, collaboration, and content usage metrics without introducing those private identifiers into the .p2lrn.

Learning events and active time API

The Web SDK alpha.11 can project runtime events to an ordered learning flow without changing the envelope p2lrn:*.

createLearningEvent()

Normalizes a runtime event with schema, version, id, sequence, category and context.

createLearningEventStream()

Preserves sequence and, optionally, sessionId/attemptId.

observeLearningEvents()

Looks at the known families in an EventTarget and outputs the normalized model.

createActivityTracker()

Measure active time considering visibility, focus and interaction. By default, the interaction is limited to the Player so as not to count third-party UI clicks.

Active time is opt-in telemetry; It does not by itself substitute a pedagogical rule, an official note or an external temporal authority.

P2LRN Analytics

When you need to query these metrics for many learners and courses, P2LRN Analytics provides an embeddable panel via CDN or NPM. Keeps observed activity and Host authoritative notes separate.