Skip to content

[QTI] Publish mixed Perseus + QTI exercises as one QTI package #6004

Description

@rtibbles

This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Overview

Let a single exercise node containing both raw Perseus questions and QTI questions publish together as one QTI package. Raw Perseus questions are wrapped in a custom QTI interaction that Kolibri's QTI Viewer renders via the Perseus renderer (Kolibri side: #9).

Complexity: High
Target branch: unstable

Context

  • Today a node publishes either Perseus or QTI, never both in one package — the two are mutually exclusive at publish.
  • Nodes mixing legacy Perseus questions with new QTI questions have no publish path.
  • A Perseus question can ride inside a QTI package as a custom interaction (see Contract) for the host's Perseus renderer to render (Kolibri side: configuration of browserify, changes to the data structure and allow … #9).
  • Kolibri gates import by renderability; a mixed package also needs the Perseus renderer, so publish declares it via File.included_presets (see contract; honored by Fixes browserify subprocess running for windows. #10).

The Change

  • Relax the one-generator-per-node selection in publish.py so a node holding both perseus_question and QTI items routes to QTI packaging instead of being forced to one format.
  • Emit each perseus_question as the Perseus custom interaction defined below, writing its Perseus JSON to a referenced package file via QTIExerciseGenerator.
  • Add included_presets to the published File mirror (kolibri_content/kolibri_public) and set it per file: its own preset's bit, plus the exercise bit for a qti File embedding ≥1 Perseus custom interaction (see contract).

Contract — Perseus-in-QTI custom interaction

Shared with the Kolibri renderer issue (learningequality/kolibri). Exact attribute validity is confirmed against the QTI 3.0 XSD under #2.

  • Each raw Perseus question is a qti-custom-interaction (the spec's delivery-engine-specific element — no JS module required) with a data-type="perseus" marker.
  • The Perseus JSON is a resource file in the package, referenced by data-perseus-path; every file it references (images, and graphie .svg/-data.json) is packaged and declared as a dependency of that resource in the manifest.
  • The host's Perseus renderer, keyed off data-type="perseus", owns rendering and grading; it grades the attempt and reports the resulting correct/incorrect back through the QTI response (no QTI-native response-processing template).
<qti-custom-interaction
    response-identifier="RESPONSE"
    data-type="perseus"
    data-perseus-path="perseus/{assessment_id}.json"/>

Contract — File.included_presets bitmask

Shared with the Kolibri import-gating issue (learningequality/kolibri).

  • Each published File carries included_presets: a bitmask of the presets whose renderer it needs, including its own preset.
  • A normal file's value is just its own preset's bit.
  • A qti File embedding ≥1 Perseus custom interaction sets the qti and exercise bits.
  • The preset→bit ordering is defined in le-utils (alongside the new QTI type).
  • Kolibri (Fixes browserify subprocess running for windows. #10) treats a node as renderable if any file's included_presets is a subset of the locally available presets.

Acceptance Criteria

  • A mixed node yields one QTI package whose manifest lists both native QTI items and Perseus custom interactions
  • Each Perseus custom interaction references its Perseus JSON and assets as package files (see Contract)
  • The package validates against the QTI 3.0 XSD (Ant's backend plus setting up front end files #2)
  • The mixed qti File has included_presets = qti | exercise; non-mixed files carry only their own preset bit
  • Native QTI questions in the same node are unaffected
  • Tests cover a mixed node and assert both question kinds appear in the package

References

AI usage

Architecture decided with the maintainer across an iterative session: blanket QTI type with the item XML in raw_data; XSD-authoritative validation across all sources; a legacy→QTI global migration with an API-layer dual-read; ricecooker upload delegating to the AssessmentItem serializer; and a Perseus custom-interaction contract confirmed against the QTI 3.0 specification. Claude mapped the existing publish/validation/ricecooker code, proposed the breakdown, and drafted each issue; the maintainer steered every decision and reviewed throughout.

Metadata

Metadata

Assignees

No one assigned

    Type

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions