Skip to content

Commit c1c7084

Browse files
rtibblesbotclaude
andcommitted
feat: add QTI assessment-item type and renderable preset-bit ordering
Expose format_presets.RENDERABLE_PRESETS_ORDER as a stable, append-only preset id ordering so Studio and Kolibri derive File.included_presets bitmask positions from shared bit locations. Fixes #224 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 66f2745 commit c1c7084

4 files changed

Lines changed: 38 additions & 0 deletions

File tree

le_utils/constants/exercises.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@
3939
SINGLE_SELECTION = "single_selection"
4040
FREE_RESPONSE = "free_response"
4141
PERSEUS_QUESTION = "perseus_question"
42+
QTI = "QTI"
4243

4344
question_choices = (
4445
(INPUT_QUESTION, "Input Question"),
4546
(MULTIPLE_SELECTION, "Multiple Selection"),
4647
(SINGLE_SELECTION, "Single Selection"),
4748
(FREE_RESPONSE, "Free Response"),
4849
(PERSEUS_QUESTION, "Perseus Question"),
50+
(QTI, "QTI"),
4951
)

le_utils/constants/format_presets.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,10 @@ def _initialize_preset_list():
159159

160160

161161
PRESETLIST = list(_initialize_preset_list())
162+
163+
# Bit order for the File.included_presets renderable bitmask.
164+
# Consumers compute the bit value as 2 ** RENDERABLE_PRESETS_ORDER.index(preset_id).
165+
# APPEND-ONLY: never reorder or remove an existing entry — that would
166+
# silently change the bit value of every preset after it and corrupt
167+
# already-persisted bitmasks. Only append new preset ids at the end.
168+
RENDERABLE_PRESETS_ORDER = [preset.id for preset in PRESETLIST if not preset.supplementary]

tests/test_exercises.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from le_utils.constants import exercises
5+
6+
7+
def test_QTI_is_in_question_choices():
8+
assert exercises.QTI == "QTI"
9+
assert (exercises.QTI, "QTI") in exercises.question_choices

tests/test_presets.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,23 @@ def test_format_presets_are_synced():
1616

1717
def test_PRESETLIST_exists():
1818
assert format_presets.PRESETLIST, "PRESETLIST did not genereate properly"
19+
20+
21+
def test_RENDERABLE_PRESETS_ORDER_matches_PRESETLIST_ids():
22+
preset_ids = {preset.id for preset in format_presets.PRESETLIST if not preset.supplementary}
23+
assert set(format_presets.RENDERABLE_PRESETS_ORDER) == preset_ids
24+
25+
26+
def test_RENDERABLE_PRESETS_ORDER_excludes_supplementary_presets():
27+
assert "video_thumbnail" not in format_presets.RENDERABLE_PRESETS_ORDER
28+
assert "qti_thumbnail" not in format_presets.RENDERABLE_PRESETS_ORDER
29+
30+
31+
def test_RENDERABLE_PRESETS_ORDER_index_is_stable():
32+
# Pin known bit positions: reordering silently breaks Studio/Kolibri's
33+
# already-persisted File.included_presets bitmask values.
34+
assert format_presets.RENDERABLE_PRESETS_ORDER.index("high_res_video") == 0
35+
assert format_presets.RENDERABLE_PRESETS_ORDER.index("low_res_video") == 1
36+
assert format_presets.RENDERABLE_PRESETS_ORDER.index("video_dependency") == 2
37+
assert format_presets.RENDERABLE_PRESETS_ORDER.index("qti") == 13
38+
assert format_presets.RENDERABLE_PRESETS_ORDER.index("kpub") == 17

0 commit comments

Comments
 (0)