diff --git a/packages/cutie-editor/src/utils/responseProcessingClassifier.spec.ts b/packages/cutie-editor/src/utils/responseProcessingClassifier.spec.ts index b4d8a7d..e307ff8 100644 --- a/packages/cutie-editor/src/utils/responseProcessingClassifier.spec.ts +++ b/packages/cutie-editor/src/utils/responseProcessingClassifier.spec.ts @@ -2,6 +2,8 @@ import { describe, expect, it } from 'vitest'; import { item as inlineFeedbackItem } from '../../../cutie-example/src/example-items/inline-feedback'; import { item as modalFeedbackItem } from '../../../cutie-example/src/example-items/modal-feedback'; import { item as choiceFeedbackItem } from '../../../cutie-example/src/example-items/standard-choice'; +import { item as textEntryItem } from '../../../cutie-example/src/example-items/standard-text-entry'; +import { item as textEntryMultiItem } from '../../../cutie-example/src/example-items/text-entry-multi'; import { classifyResponseProcessing } from './responseProcessingClassifier'; /** @@ -285,6 +287,44 @@ describe('responseProcessingClassifier', () => { expect(result.mode).toBe('sumScores'); }); + it('should recognize sumScores + mapped response feedback (qti-gt > qti-map-response)', () => { + const doc = createQtiDoc(` + + + + + + + + + + + + 0 + + + + + RESPONSE_correct + + + + + + + + RESPONSE_incorrect + + + + + + `); + + const result = classifyResponseProcessing(doc); + expect(result.mode).toBe('sumScores'); + }); + it('should reject sumScores if additional condition sets non-FEEDBACK outcome', () => { const doc = createQtiDoc(` @@ -338,5 +378,17 @@ describe('responseProcessingClassifier', () => { const result = classifyResponseProcessing(doc); expect(result.mode).toBe('allCorrect'); }); + + it('should classify standard-text-entry.ts as sumScores', () => { + const doc = parseItem(textEntryItem); + const result = classifyResponseProcessing(doc); + expect(result.mode).toBe('sumScores'); + }); + + it('should classify text-entry-multi.ts as sumScores', () => { + const doc = parseItem(textEntryMultiItem); + const result = classifyResponseProcessing(doc); + expect(result.mode).toBe('sumScores'); + }); }); }); diff --git a/packages/cutie-editor/src/utils/responseProcessingGenerator.ts b/packages/cutie-editor/src/utils/responseProcessingGenerator.ts index 516ec44..ce1b756 100644 --- a/packages/cutie-editor/src/utils/responseProcessingGenerator.ts +++ b/packages/cutie-editor/src/utils/responseProcessingGenerator.ts @@ -270,7 +270,8 @@ function generateSumScoresXml( responseIdentifiers, responseDeclarations, feedbackIdentifiersUsed, - doc + doc, + new Set(withMapping) ); for (const fc of feedbackConditions) { responseProcessing.appendChild(fc); @@ -381,7 +382,8 @@ function generateFeedbackProcessingXml( responseIdentifiers: string[], responseDeclarations: Map, feedbackIdentifiersUsed: Set, - doc: Document + doc: Document, + mappedResponseIds?: Set ): Element[] { const conditions: Element[] = []; @@ -413,12 +415,15 @@ function generateFeedbackProcessingXml( const hasIncorrect = feedbackIds.has(incorrectId); if (hasCorrect || hasIncorrect) { - // Generate correct/incorrect condition + // For responses with mappings, use qti-map-response > 0 instead of qti-match + // so that feedback agrees with case-insensitive mapped scoring + const useMapResponse = mappedResponseIds?.has(responseId) ?? false; const condition = createFeedbackCorrectIncorrectCondition( responseId, hasCorrect ? correctId : null, hasIncorrect ? incorrectId : null, - doc + doc, + useMapResponse ); conditions.push(condition); } @@ -445,26 +450,32 @@ function generateFeedbackProcessingXml( /** * Create a feedback condition for correct/incorrect responses. * + * For unmapped responses (useMapResponse=false), uses qti-match: * * * * * * - * - * - * - * RESPONSE_correct - * - * + * ... * * - * - * - * - * RESPONSE_incorrect - * - * + * ... + * + * + * + * For mapped responses (useMapResponse=true), uses qti-gt with qti-map-response + * so that feedback agrees with case-insensitive mapped scoring: + * + * + * + * + * 0 + * + * ... + * + * + * ... * * */ @@ -472,13 +483,18 @@ function createFeedbackCorrectIncorrectCondition( responseId: string, correctFeedbackId: string | null, incorrectFeedbackId: string | null, - doc: Document + doc: Document, + useMapResponse: boolean = false ): Element { const condition = doc.createElementNS(QTI_NAMESPACE, 'qti-response-condition'); // Response if - when correct const responseIf = doc.createElementNS(QTI_NAMESPACE, 'qti-response-if'); - responseIf.appendChild(createMatchElement(responseId, doc)); + if (useMapResponse) { + responseIf.appendChild(createMapResponseGtZeroElement(responseId, doc)); + } else { + responseIf.appendChild(createMatchElement(responseId, doc)); + } if (correctFeedbackId) { responseIf.appendChild(createSetFeedbackElement(correctFeedbackId, doc)); @@ -496,6 +512,26 @@ function createFeedbackCorrectIncorrectCondition( return condition; } +/** + * Create a qti-gt element that checks if a mapped response score is greater than 0. + * Used for feedback conditions on responses with mappings, so that feedback + * uses the same case-insensitive matching as the scoring. + */ +function createMapResponseGtZeroElement(identifier: string, doc: Document): Element { + const gt = doc.createElementNS(QTI_NAMESPACE, 'qti-gt'); + + const mapResponse = doc.createElementNS(QTI_NAMESPACE, 'qti-map-response'); + mapResponse.setAttribute('identifier', identifier); + gt.appendChild(mapResponse); + + const baseValue = doc.createElementNS(QTI_NAMESPACE, 'qti-base-value'); + baseValue.setAttribute('base-type', 'float'); + baseValue.textContent = '0'; + gt.appendChild(baseValue); + + return gt; +} + /** * Create a feedback condition for a specific choice selection. * diff --git a/packages/cutie-example/src/example-items/standard-text-entry.ts b/packages/cutie-example/src/example-items/standard-text-entry.ts index 5f106a8..2ff2e10 100644 --- a/packages/cutie-example/src/example-items/standard-text-entry.ts +++ b/packages/cutie-example/src/example-items/standard-text-entry.ts @@ -60,10 +60,10 @@ adaptive="false" time-dependent="false" xml:lang="en"> - - - - + + + 0 + diff --git a/packages/cutie-example/src/example-items/text-entry-multi.ts b/packages/cutie-example/src/example-items/text-entry-multi.ts index 89f5361..fc8b153 100644 --- a/packages/cutie-example/src/example-items/text-entry-multi.ts +++ b/packages/cutie-example/src/example-items/text-entry-multi.ts @@ -10,7 +10,7 @@ https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="text-entry-multi" title="Text Entry - Multiple in Paragraph" adaptive="false" time-dependent="false" xml:lang="en"> - + 1776 @@ -19,7 +19,7 @@ adaptive="false" time-dependent="false" xml:lang="en"> - + Philadelphia @@ -29,7 +29,7 @@ adaptive="false" time-dependent="false" xml:lang="en"> - + thirteen @@ -55,38 +55,51 @@ adaptive="false" time-dependent="false" xml:lang="en">

Fill in the blanks to complete the paragraph about American history.

The Declaration of Independence was signed in the year - . + . The document was adopted by the Continental Congress meeting in - , + , Pennsylvania. At the time, there were - + colonies that declared their independence from British rule.

-

Excellent! You have correctly identified the key facts about the Declaration of Independence. This foundational document established the principles of American democracy and marked the birth of a new nation.

+

Correct! The Declaration of Independence was signed in 1776.

- -

Not quite. Review the timeline of the American Revolution and the founding of the United States. Consider when the Continental Congress met and how many colonies participated in the independence movement.

+

Incorrect. Consider the key dates of the American Revolution.

+
+ + +

Correct! The Continental Congress met in Philadelphia.

+
+ +

Incorrect. Think about where the Continental Congress convened in Pennsylvania.

+
+ + +

Correct! There were thirteen original colonies.

+
+ +

Incorrect. Review how many colonies participated in the independence movement.

- - - + + + - - - - + + + 0 + @@ -103,6 +116,52 @@ adaptive="false" time-dependent="false" xml:lang="en"> + + + + + + 0 + + + + + RESPONSE_2_correct + + + + + + + + RESPONSE_2_incorrect + + + + + + + + + + 0 + + + + + RESPONSE_3_correct + + + + + + + + RESPONSE_3_incorrect + + + + `;