From 9e0d6feae1cbe1b892d8caf6672e04e6a1ff549f Mon Sep 17 00:00:00 2001 From: Anders Weinstein Date: Fri, 3 Oct 2025 16:07:41 -0400 Subject: [PATCH 1/3] preserve custom points on instructor-graded questions; set selection points per question --- src/convert.ts | 36 +++++++++++++++++++++++++++++++ src/index.ts | 1 + src/resources/formative.ts | 8 +++---- src/resources/image.ts | 1 - src/resources/questions/cata.ts | 1 - src/resources/questions/common.ts | 7 ++++-- src/resources/questions/multi.ts | 11 ++++++---- src/resources/superactivity.ts | 1 - 8 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/convert.ts b/src/convert.ts index 71300287..e10a297f 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -644,6 +644,42 @@ export function fixWildcardSelections(resources: TorusResource[]) { return resources; } +const getSelectionPoints = (resources: TorusResource[], sel: any) => { + const tag = sel.logic.conditions.children[0].value[0]; + const acts = resources.filter( + (r) => r.type === 'Activity' && r.tags.includes(tag) + ); + + const partPoints = (act: any): number[] => + act.content.authoring.parts.map((part: any) => part?.outOf || 1); + + const actPoints = (act: any) => + partPoints(act).reduce((sum: number, val: number) => sum + val, 0); + + // we can only assign points to selection if all possible questions have same points + const firstActPoints = actPoints(acts[0]); + return acts.every((a) => actPoints(a) === firstActPoints) + ? firstActPoints + : 1; +}; + +export function setSelectionPoints(resources: TorusResource[]) { + resources + .filter((r) => r.type === 'Page') + .forEach((page) => { + getDescendants( + (page as Page).content.model as any[], + 'selection' + ).forEach((sel: any) => { + const points = getSelectionPoints(resources, sel); + if (points !== 1) { + sel.pointsPerActivity = points; + } + }); + }); + + return resources; +} function fixReportActivityid(resources: TorusResource[], selection: any) { resources .filter((r) => r.type === 'Page' && r.id === selection.activityId) diff --git a/src/index.ts b/src/index.ts index 0b99989c..4589acf2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -292,6 +292,7 @@ export function convertAction(options: CmdOptions): Promise { updated = Convert.updateDerivativeReferences(updated); updated = Convert.generatePoolTags(updated); updated = Convert.fixWildcardSelections(updated); + updated = Convert.setSelectionPoints(updated); updated = Convert.fixActivityReports(updated); updated = filterOutTemporaryContent(updated); updated = Convert.updateNonDirectImageReferences( diff --git a/src/resources/formative.ts b/src/resources/formative.ts index 62d12ce0..5a57948c 100644 --- a/src/resources/formative.ts +++ b/src/resources/formative.ts @@ -75,7 +75,6 @@ function buildMCQPart(question: any) { content: Common.ensureParagraphs(r.children), })) ), - scoringStrategy: 'average', targeted: [], objectives: skillrefs.map((s: any) => s.idref), explanation: Common.maybeBuildPartExplanation(responses), @@ -161,7 +160,6 @@ function buildOrderingPart(question: any) { content: Common.ensureParagraphs(r.children), })) ), - scoringStrategy: 'average', objectives: skillrefs.map((s: any) => s.idref), explanation: Common.maybeBuildPartExplanation(responses), }; @@ -215,7 +213,6 @@ function buildLikertParts(question: any, items: any[]) { }, Common.makeCatchAllResponse(), ], - scoringStrategy: 'average', objectives: [], targeted: [], explanation: null, @@ -483,7 +480,7 @@ export function toActivity( } // add optional custom scoring attributes to model if needed - setCustomScoringFlags(model, activity.subType); + setCustomScoringFlags(model, activity.subType, activity.id); // collect refs from any internal links in stem content const links: any[] = Common.getDescendants(model.stem?.content, 'a'); @@ -553,10 +550,11 @@ export function titleActivity( } // add optional attributes to flag custom scoring to torus authoring -export function setCustomScoringFlags(model: any, subType: string) { +export function setCustomScoringFlags(model: any, subType: string, id: string) { const hasCustomPoints = model.authoring.parts.some( (p: any) => Common.getOutOfPoints(p) > 1 ); + if (hasCustomPoints) { // For multi-part questions, authoring detects custom by activity-wide flag if (['oli_multi_input', 'oli_response_multi'].includes(subType)) diff --git a/src/resources/image.ts b/src/resources/image.ts index 923add10..20015a7c 100644 --- a/src/resources/image.ts +++ b/src/resources/image.ts @@ -162,7 +162,6 @@ function defaultContent(example: boolean) { ], }, ], - scoringStrategy: 'average', }, ], transformations: [], diff --git a/src/resources/questions/cata.ts b/src/resources/questions/cata.ts index 83cc89de..d74ecd31 100644 --- a/src/resources/questions/cata.ts +++ b/src/resources/questions/cata.ts @@ -204,7 +204,6 @@ export function buildCATAPart(question: any) { content: Common.ensureParagraphs(r.children), })) ), - scoringStrategy: 'average', objectives: skillrefs.map((s: any) => s.idref), explanation: Common.maybeBuildPartExplanation(responses), targeted: [], diff --git a/src/resources/questions/common.ts b/src/resources/questions/common.ts index 4560ab4b..d1efab04 100644 --- a/src/resources/questions/common.ts +++ b/src/resources/questions/common.ts @@ -378,7 +378,6 @@ export function buildTextPart(id: string, question: any) { })) ), objectives: skillrefs.map((s: any) => s.idref), - scoringStrategy: 'average', explanation: maybeBuildPartExplanation(legacyResponses), gradingApproach: getGradingApproach(question), }; @@ -432,5 +431,9 @@ export function adjustSubmitCompareResponses(origResponses: any[]) { } export function getOutOfPoints(part: any) { - return Math.max(...part.responses.map((r: any) => r?.score ?? 0)); + return ( + // instructor-graded questions have no responses but may still have custom + // outOf points set from score_out_of attribute on legacy part + part?.outOf || Math.max(...part.responses.map((r: any) => r?.score ?? 0)) + ); } diff --git a/src/resources/questions/multi.ts b/src/resources/questions/multi.ts index 480159ac..58c742db 100644 --- a/src/resources/questions/multi.ts +++ b/src/resources/questions/multi.ts @@ -396,7 +396,6 @@ function buildDropdownPart(part: any, i: number, ignorePartId: boolean) { })) ), objectives: skillrefs.map((s: any) => s.idref), - scoringStrategy: 'average', explanation: Common.maybeBuildPartExplanation(responses), }; } @@ -461,7 +460,7 @@ export function buildInputPart( const skillrefs = Common.getChildren(part, 'skillref'); const id = part.id !== undefined && part.id !== null ? part.id + '' : guid(); - return { + const torusPart: any = { id, responses: responses.map((r: any) => { const cleanedMatch = convertCatchAll(r.match); @@ -488,10 +487,15 @@ export function buildInputPart( })) ), objectives: skillrefs.map((s: any) => s.idref), - scoringStrategy: 'average', explanation: Common.maybeBuildPartExplanation(responses), gradingApproach: Common.getGradingApproach(input), }; + // include custom outOf points for instructor-graded questions if specified by score_out_of attr + if (torusPart.gradingApproach === 'manual' && part?.score_out_of) { + torusPart.outOf = Number(part.score_out_of); + } + + return torusPart; } // Build a response_multi question. Similar to multi-input, but each part subsumes a *set* of @@ -611,7 +615,6 @@ const toResponseMultiPart = (part: any, items: any[]) => { })) ), objectives: skillrefs.map((s: any) => s.idref), - scoringStrategy: 'average', explanation: Common.maybeBuildPartExplanation(responses), }; }; diff --git a/src/resources/superactivity.ts b/src/resources/superactivity.ts index a8877c7e..20bcb5ec 100644 --- a/src/resources/superactivity.ts +++ b/src/resources/superactivity.ts @@ -265,7 +265,6 @@ function toActivityModel( parts: [ { id: guid(), - scoringStrategy: 'average', responses: [], hints: [], }, From 04b1f0b619aa89fdcfd53f9efa2a67e8ff68a21f Mon Sep 17 00:00:00 2001 From: Anders Weinstein Date: Fri, 3 Oct 2025 19:42:13 -0400 Subject: [PATCH 2/3] remove unused parameter --- src/resources/formative.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resources/formative.ts b/src/resources/formative.ts index 5a57948c..3d216b9b 100644 --- a/src/resources/formative.ts +++ b/src/resources/formative.ts @@ -480,7 +480,7 @@ export function toActivity( } // add optional custom scoring attributes to model if needed - setCustomScoringFlags(model, activity.subType, activity.id); + setCustomScoringFlags(model, activity.subType); // collect refs from any internal links in stem content const links: any[] = Common.getDescendants(model.stem?.content, 'a'); @@ -550,7 +550,7 @@ export function titleActivity( } // add optional attributes to flag custom scoring to torus authoring -export function setCustomScoringFlags(model: any, subType: string, id: string) { +export function setCustomScoringFlags(model: any, subType: string) { const hasCustomPoints = model.authoring.parts.some( (p: any) => Common.getOutOfPoints(p) > 1 ); From 29555acb7e3a20ace142b097eb27e21aa80158d5 Mon Sep 17 00:00:00 2001 From: Anders Weinstein Date: Fri, 3 Oct 2025 19:52:39 -0400 Subject: [PATCH 3/3] update tests for removal of scoringStrategy --- test/resources/feedback-test.ts | 11 +++++------ test/resources/summative-test.ts | 2 -- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/test/resources/feedback-test.ts b/test/resources/feedback-test.ts index cc551665..41f4bf0d 100644 --- a/test/resources/feedback-test.ts +++ b/test/resources/feedback-test.ts @@ -430,7 +430,6 @@ describe('convert feedback', () => { }, }, ], - scoringStrategy: 'average', objectives: expect.any(Array), targeted: expect.any(Array), explanation: null, @@ -477,7 +476,7 @@ describe('convert feedback', () => { }, }, ], - scoringStrategy: 'average', + objectives: expect.any(Array), targeted: expect.any(Array), explanation: null, @@ -524,7 +523,7 @@ describe('convert feedback', () => { }, }, ], - scoringStrategy: 'average', + objectives: expect.any(Array), targeted: expect.any(Array), explanation: null, @@ -657,7 +656,7 @@ describe('convert feedback', () => { }, }, ], - scoringStrategy: 'average', + objectives: expect.any(Array), targeted: expect.any(Array), explanation: null, @@ -730,7 +729,7 @@ describe('convert feedback', () => { }, ], objectives: [], - scoringStrategy: 'average', + explanation: null, }, ], @@ -1071,7 +1070,7 @@ describe('convert feedback', () => { content: [{ type: 'p', children: [{ text: '' }] }], }, ], - scoringStrategy: 'average', + targeted: expect.any(Array), objectives: [], explanation: null, diff --git a/test/resources/summative-test.ts b/test/resources/summative-test.ts index c8e020f4..0b8db125 100644 --- a/test/resources/summative-test.ts +++ b/test/resources/summative-test.ts @@ -235,7 +235,6 @@ describe('convert summative', () => { content: [{ type: 'p', children: [{ text: '' }] }], }, ], - scoringStrategy: 'average', targeted: [], objectives: [], explanation: null, @@ -342,7 +341,6 @@ describe('convert summative', () => { content: [{ type: 'p', children: [{ text: '' }] }], }, ], - scoringStrategy: 'average', targeted: [], objectives: [], explanation: null,