Skip to content

Implement QTI expression evaluation, response processing, and context composable#14631

Open
rtibbles wants to merge 7 commits intolearningequality:developfrom
rtibbles:qti_outcome_evaluation
Open

Implement QTI expression evaluation, response processing, and context composable#14631
rtibbles wants to merge 7 commits intolearningequality:developfrom
rtibbles:qti_outcome_evaluation

Conversation

@rtibbles
Copy link
Copy Markdown
Member

Summary

Implements the non-item-body parts of the QTI AssessmentItem — variable declarations, expression evaluation, response processing, and the useQTIContext composable that wires them together. AssessmentItem now captures responses, evaluates response processing, and exposes outcome variables back to the surrounding context.

Architecture follows the plan laid out in #13539:

  • Variable declarations use strategy classes per QTI declaration kind (default value, correct response, mapping, areaMapping, lookupTable, template defaults). A single variable is composed from whichever declaration fragments its XML contains, rather than a monolithic parser branching on every shape.

  • Expression evaluation — a tree-walking interpreter for the QTI expression grammar. The evaluator handles ~84 operators covering:

    • arithmetic, logic (three-valued), set and multiset operations
    • string pattern matching via XSD regex (xspattern)
    • geometric hotspot predicates
    • container cardinality (single / multiple / ordered / record)
    • tolerance-based numeric comparison (exact / absolute / relative)
    • rounding (significantFigures / decimalPlaces)
    • context lookups across response, outcome, and template variables

    Base type and cardinality are inferred from declarations at parse time so expressions can be validated before evaluation. Evaluation is a pure function over a context — it reads variables but never mutates them, keeping response processing deterministic and testable.

  • Response processing walks the QTI rule tree (responseCondition, setOutcomeValue, exitResponse, template resolution, …) driving the evaluator for its expression children and assigning outcome variables as a side effect at the rule level only.

  • Context hierarchy flows via provide/inject: QTIViewer → AssessmentTest → AssessmentItem, with inner scopes overriding outer. The merged context is exposed as a computed, so it rebuilds when the underlying XML changes.

No UI changes in this PR — the surface visible to users is the same; the difference is that response values are now captured, typed, and processed against the item's response processing rules.

References

Reviewer guidance

Open /qti_sandbox via pnpm devserver and pick a fixture item. The Answer State and Outcomes panels auto-populate from checkAnswer. The XML editor is live for ad-hoc experiments. Expression semantics are exercised in utils/qti/__tests__/evaluator.spec.js.

Areas worth particular attention:

  • utils/qti/evaluator.js — expression interpreter. NULL propagation is per-operator. Two inline-flagged interpretations: qti-match ordered/multiple equality and qti-any-n three-valued logic.
  • utils/qti/variables.js + declarations/ — declaration strategy pattern: each child element registers a capability on the owning QTIVariable.
  • utils/qti/responseProcessing.js — rule-tree walker driving the evaluator.
  • composables/useDeclarations.js — version-counter trick for Vue reactivity over non-proxied QTIVariable caches.
  • composables/useContextHierarchy.js — only the item layer is exercised; the test layer is tracked in QTI: Test-level metadata, quiz navigation, and enhanced quiz types #14622.
  • composables/useResponseProcessingTemplate.js — async resolution with stale-guarding when xmlDoc swaps mid-resolve.
  • components/AssessmentItem.vue — integration surface.

AI usage

Built with Claude Code (Opus), iterating through plan / refactor / review cycles:

  • Planned alongside a QTI 3.0 processing spec document Claude generated as a verification reference during implementation.
  • Mid-course architecture refactor restructuring phase-based code into the declaration-strategy pattern now in utils/qti/declarations/.
  • Several rounds of human review that drove splitting useQTIContext into its four component composables, replacing inline test XML with fixture items, and tightening JSDoc.
  • A final review resolving every §-style spec reference to a v3 Info Model URL anchor, verified against a local copy of the spec.

Claude wrote the bulk of the evaluator and its tests; I directed the architecture and verified per-operator behaviour against the spec.

@github-actions github-actions Bot added DEV: renderers HTML5 apps, videos, exercises, etc. DEV: frontend SIZE: very large labels Apr 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 22, 2026

@rtibbles rtibbles force-pushed the qti_outcome_evaluation branch from e0cf1f1 to 7b3ce6f Compare April 22, 2026 14:26
@github-actions github-actions Bot added the DEV: dev-ops Continuous integration & deployment label Apr 22, 2026
@rtibbles rtibbles force-pushed the qti_outcome_evaluation branch from 7b3ce6f to 4b06f6f Compare April 22, 2026 15:09
rtibbles and others added 7 commits April 22, 2026 09:14
QTIVariable uses a capability registration pattern where self-contained
declaration classes parse XML child elements and register capabilities
(defaultValue, correctResponse, score, lookup) on the variable. Consumers
call the variable's own methods without knowing which strategy provides them.

Declaration classes: DefaultValue, CorrectResponse, Mapping, AreaMapping,
LookupTable. Mapping and AreaMapping share a ScoringDeclaration base class.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Pure function evaluator supporting arithmetic, comparison, logical,
container, mapping, statistical, and test-level QTI expression operators.
Three-valued null logic per QTI 3.0 spec. Bridge cases delegate to
variable capability methods (score, lookup) for declaration-specific behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Response processing is a pure control flow engine (conditions, branching,
exit) with registry-based dispatch for declaration-specific rules.
Built-in templates (match_correct, map_response, map_response_point) are
QTI XML strings resolved by URI — same code path as custom templates.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ment

Vue composable providing hierarchical context management, reactive variable
declarations, computed expression evaluation, response processing with
template resolution, and provide/inject integration for the component tree.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ssing

Replace the stub hard-coded correct:1 return value in AssessmentItem
with actual QTI response processing via useQTIContext. The checkAnswer
callback now runs processResponses() to compute outcome variables
(e.g., SCORE) based on the candidate's responses, returning computed
outcomes alongside the answer state.

- Use useQTIContext composable for response/outcome declaration
  management, eliminating duplicate getQTIDeclarations and clearObject
- Bridge the interaction handler via onValueChange callback
- Return {outcomes, answerState} from checkAnswer instead of {correct: 1}
- Add 8 integration tests covering match_correct and map_response
  scoring, lower bound clamping, answer state, and response provision

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Adds panels that auto-populate from checkAnswer on viewer start, item
swap, and interaction. Splits user-typed seed values (passed as the
:answerState prop) from the live response snapshot (driven by
checkAnswer output), so the UI reflects current state without looping
back into the prop.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The QTI viewer's items.js fixture is stored via Git LFS and is now
imported by frontend test specs, so the frontend-tests workflow
needs to resolve LFS content on checkout rather than pulling the
pointer file.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@rtibbles rtibbles force-pushed the qti_outcome_evaluation branch from 4b06f6f to c3d3b07 Compare April 22, 2026 16:32
@rtibbles rtibbles marked this pull request as ready for review April 22, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DEV: dev-ops Continuous integration & deployment DEV: frontend DEV: renderers HTML5 apps, videos, exercises, etc. SIZE: very large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create composable for handling non-item body parts of the assessment item

1 participant