Implement QTI expression evaluation, response processing, and context composable#14631
Open
rtibbles wants to merge 7 commits intolearningequality:developfrom
Open
Implement QTI expression evaluation, response processing, and context composable#14631rtibbles wants to merge 7 commits intolearningequality:developfrom
rtibbles wants to merge 7 commits intolearningequality:developfrom
Conversation
Contributor
Build Artifacts
Smoke test screenshot |
e0cf1f1 to
7b3ce6f
Compare
7b3ce6f to
4b06f6f
Compare
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]>
4b06f6f to
c3d3b07
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the non-item-body parts of the QTI AssessmentItem — variable declarations, expression evaluation, response processing, and the
useQTIContextcomposable 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:
xspattern)exact/absolute/relative)significantFigures/decimalPlaces)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_sandboxviapnpm devserverand pick a fixture item. The Answer State and Outcomes panels auto-populate fromcheckAnswer. The XML editor is live for ad-hoc experiments. Expression semantics are exercised inutils/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-matchordered/multiple equality andqti-any-nthree-valued logic.utils/qti/variables.js+declarations/— declaration strategy pattern: each child element registers a capability on the owningQTIVariable.utils/qti/responseProcessing.js— rule-tree walker driving the evaluator.composables/useDeclarations.js— version-counter trick for Vue reactivity over non-proxiedQTIVariablecaches.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 whenxmlDocswaps mid-resolve.components/AssessmentItem.vue— integration surface.AI usage
Built with Claude Code (Opus), iterating through plan / refactor / review cycles:
utils/qti/declarations/.useQTIContextinto its four component composables, replacing inline test XML with fixture items, and tightening JSDoc.§-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.