Skip to content

feat: derive Perseus archives from Perseus-expressible QTI items#6035

Open
rtibblesbot wants to merge 4 commits into
learningequality:unstablefrom
rtibblesbot:issue-6001-e8b010
Open

feat: derive Perseus archives from Perseus-expressible QTI items#6035
rtibblesbot wants to merge 4 commits into
learningequality:unstablefrom
rtibblesbot:issue-6001-e8b010

Conversation

@rtibblesbot

@rtibblesbot rtibblesbot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Older Kolibri renders Perseus while the authored source of truth is now native QTI XML, so a node authored entirely as native QTI would fail to render on those clients. When every assessment item on a node is a native QTI interaction Perseus can express (single/multiple choice, text/math input), this derives a Perseus archive from the QTI XML and publishes it alongside the QTI package; a node containing any non-expressible interaction publishes QTI only, never a partial or invalid Perseus.

References

Closes #6001. Builds on the hint-catalog contract from #6011.

Reviewer guidance

  • utils/publish.py:261_node_is_perseus_derivable gates Perseus emission on every item being expressible; confirm a mixed node (one derivable + one non-expressible native QTI item) degrades to QTI-only rather than emitting a partial archive.
  • utils/publish.py:256 — the generalized stale-preset cleanup now diffs against the full generator list; verify it never deletes a preset a node still needs when both archives are produced.
  • utils/assessment/qti/perseus_derive.py — all parse/derive failures log + return None/False; confirm a single malformed raw_data cannot abort the channel publish.

AI usage

Used Claude Code to implement the reverse QTI→Perseus derivation pipeline. Verified with the touched pytest suites (tests/utils/qti/, test_exercise_creation.py, test_exportchannel.py) and flake8/black.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?
  • Ran pre-flight CI checks (lint, format, tests) and verified all pass
  • Rebased onto the target branch and resolved any conflicts
  • Reorganized commit history into clean, logical commits
  • Audited the diff to ensure only issue-relevant files are changed
  • Built PR body from the repository's PR template with evidence blocks
@rtibblesbot

🟡 Waiting for feedback

Last updated: 2026-07-10 00:04 UTC

rtibblesbot and others added 4 commits July 9, 2026 16:54
Reverse of render_markdown for the Perseus-expressible subset: flow-content
lxml elements (p/div/img/math/strong/em/br) back to Perseus markdown, dropping
interaction placeholders and re-adding the CONTENTSTORAGE image prefix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parse a type==QTI item's raw_data and, when its single interaction is
choice or text-entry, produce a DerivedAssessmentItem proxy carrying the
legacy type/question/answers/hints; return None (log + skip) otherwise.
Hints are read back from kolibri-hint catalog cards (learningequality#6011 contract).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
process_assessment_item swaps in the derived proxy for type==QTI items,
skipping non-expressible ones, so create_exercise_archive emits Perseus
item JSON for choice/text-entry QTI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
recurse_nodes emits both the QTI package and a Perseus archive when a node
has native QTI items and every item is Perseus-expressible; QTI only
otherwise. Stale-preset cleanup generalized to the generator list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rtibblesbot rtibblesbot marked this pull request as ready for review July 10, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[QTI] Dual-publish Perseus zips for choice/text-entry (math input only) QTI items

1 participant