@longsightgroup/qti3-core 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -3
- package/dist/adaptive-turn-materializer.d.ts +14 -0
- package/dist/adaptive-turn-materializer.d.ts.map +1 -0
- package/dist/adaptive-turn-materializer.js +56 -0
- package/dist/adaptive-turn-materializer.js.map +1 -0
- package/dist/adaptive-turn.d.ts +23 -0
- package/dist/adaptive-turn.d.ts.map +1 -0
- package/dist/adaptive-turn.js +60 -0
- package/dist/adaptive-turn.js.map +1 -0
- package/dist/assert-never.d.ts +3 -0
- package/dist/assert-never.d.ts.map +1 -0
- package/dist/assert-never.js +5 -0
- package/dist/assert-never.js.map +1 -0
- package/dist/attempt-state-constants.d.ts +6 -0
- package/dist/attempt-state-constants.d.ts.map +1 -0
- package/dist/attempt-state-constants.js +6 -0
- package/dist/attempt-state-constants.js.map +1 -0
- package/dist/attempt-state.d.ts +5 -0
- package/dist/attempt-state.d.ts.map +1 -0
- package/dist/attempt-state.js +190 -0
- package/dist/attempt-state.js.map +1 -0
- package/dist/catalog.js +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/content-text.d.ts.map +1 -1
- package/dist/content-text.js +3 -0
- package/dist/content-text.js.map +1 -1
- package/dist/custom-operators.d.ts +13 -0
- package/dist/custom-operators.d.ts.map +1 -0
- package/dist/custom-operators.js +2 -0
- package/dist/custom-operators.js.map +1 -0
- package/dist/delivery-redaction.d.ts +50 -0
- package/dist/delivery-redaction.d.ts.map +1 -0
- package/dist/delivery-redaction.js +176 -0
- package/dist/delivery-redaction.js.map +1 -0
- package/dist/delivery-security.d.ts +3 -18
- package/dist/delivery-security.d.ts.map +1 -1
- package/dist/delivery-security.js +35 -189
- package/dist/delivery-security.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/parser-custom-interactions.js +1 -1
- package/dist/parser-custom-interactions.js.map +1 -1
- package/dist/parser-declarations.d.ts.map +1 -1
- package/dist/parser-declarations.js +4 -4
- package/dist/parser-declarations.js.map +1 -1
- package/dist/parser-item-metadata.d.ts.map +1 -1
- package/dist/parser-item-metadata.js +8 -7
- package/dist/parser-item-metadata.js.map +1 -1
- package/dist/parser-processing.js +2 -2
- package/dist/parser-processing.js.map +1 -1
- package/dist/parser-values.d.ts +5 -1
- package/dist/parser-values.d.ts.map +1 -1
- package/dist/parser-values.js +33 -0
- package/dist/parser-values.js.map +1 -1
- package/dist/processing-evaluator-boolean.d.ts +8 -0
- package/dist/processing-evaluator-boolean.d.ts.map +1 -0
- package/dist/processing-evaluator-boolean.js +47 -0
- package/dist/processing-evaluator-boolean.js.map +1 -0
- package/dist/processing-evaluator-collection.d.ts +12 -0
- package/dist/processing-evaluator-collection.d.ts.map +1 -0
- package/dist/processing-evaluator-collection.js +35 -0
- package/dist/processing-evaluator-collection.js.map +1 -0
- package/dist/processing-evaluator-comparison.d.ts +8 -0
- package/dist/processing-evaluator-comparison.d.ts.map +1 -0
- package/dist/processing-evaluator-comparison.js +88 -0
- package/dist/processing-evaluator-comparison.js.map +1 -0
- package/dist/processing-evaluator-custom.d.ts +8 -0
- package/dist/processing-evaluator-custom.d.ts.map +1 -0
- package/dist/processing-evaluator-custom.js +14 -0
- package/dist/processing-evaluator-custom.js.map +1 -0
- package/dist/processing-evaluator-geometry.d.ts +8 -0
- package/dist/processing-evaluator-geometry.d.ts.map +1 -0
- package/dist/processing-evaluator-geometry.js +21 -0
- package/dist/processing-evaluator-geometry.js.map +1 -0
- package/dist/processing-evaluator-numeric.d.ts +8 -0
- package/dist/processing-evaluator-numeric.d.ts.map +1 -0
- package/dist/processing-evaluator-numeric.js +112 -0
- package/dist/processing-evaluator-numeric.js.map +1 -0
- package/dist/processing-evaluator-string.d.ts +8 -0
- package/dist/processing-evaluator-string.d.ts.map +1 -0
- package/dist/processing-evaluator-string.js +33 -0
- package/dist/processing-evaluator-string.js.map +1 -0
- package/dist/processing-evaluator-variable.d.ts +8 -0
- package/dist/processing-evaluator-variable.d.ts.map +1 -0
- package/dist/processing-evaluator-variable.js +39 -0
- package/dist/processing-evaluator-variable.js.map +1 -0
- package/dist/processing-evaluator.d.ts +22 -0
- package/dist/processing-evaluator.d.ts.map +1 -0
- package/dist/processing-evaluator.js +137 -0
- package/dist/processing-evaluator.js.map +1 -0
- package/dist/processing-expression-children.d.ts +3 -0
- package/dist/processing-expression-children.d.ts.map +1 -0
- package/dist/processing-expression-children.js +68 -0
- package/dist/processing-expression-children.js.map +1 -0
- package/dist/processing-mapping.d.ts +13 -0
- package/dist/processing-mapping.d.ts.map +1 -0
- package/dist/processing-mapping.js +120 -0
- package/dist/processing-mapping.js.map +1 -0
- package/dist/processing-operators.d.ts +11 -0
- package/dist/processing-operators.d.ts.map +1 -0
- package/dist/processing-operators.js +178 -0
- package/dist/processing-operators.js.map +1 -0
- package/dist/processing-random.d.ts +2 -0
- package/dist/processing-random.d.ts.map +1 -0
- package/dist/processing-random.js +16 -0
- package/dist/processing-random.js.map +1 -0
- package/dist/processing-state.d.ts +8 -0
- package/dist/processing-state.d.ts.map +1 -0
- package/dist/processing-state.js +44 -0
- package/dist/processing-state.js.map +1 -0
- package/dist/processing-templates.d.ts +3 -0
- package/dist/processing-templates.d.ts.map +1 -0
- package/dist/processing-templates.js +12 -0
- package/dist/processing-templates.js.map +1 -0
- package/dist/processing-values.d.ts +15 -0
- package/dist/processing-values.d.ts.map +1 -0
- package/dist/processing-values.js +150 -0
- package/dist/processing-values.js.map +1 -0
- package/dist/processing-variables.d.ts +7 -0
- package/dist/processing-variables.d.ts.map +1 -0
- package/dist/processing-variables.js +40 -0
- package/dist/processing-variables.js.map +1 -0
- package/dist/serializer-processing-expressions.d.ts.map +1 -1
- package/dist/serializer-processing-expressions.js +2 -8
- package/dist/serializer-processing-expressions.js.map +1 -1
- package/dist/serializer-processing-xml.js +2 -2
- package/dist/serializer-processing-xml.js.map +1 -1
- package/dist/serializer-processing.d.ts.map +1 -1
- package/dist/serializer-processing.js +3 -0
- package/dist/serializer-processing.js.map +1 -1
- package/dist/server-scoring.d.ts +4 -14
- package/dist/server-scoring.d.ts.map +1 -1
- package/dist/server-scoring.js +19 -157
- package/dist/server-scoring.js.map +1 -1
- package/dist/session.d.ts +5 -14
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +76 -1158
- package/dist/session.js.map +1 -1
- package/dist/shared-vocabulary-coverage-policy.js +1 -1
- package/dist/shared-vocabulary-coverage-policy.js.map +1 -1
- package/dist/shared-vocabulary-generated-families.js +1 -1
- package/dist/shared-vocabulary-generated-families.js.map +1 -1
- package/dist/shared-vocabulary-support.js +3 -3
- package/dist/shared-vocabulary-support.js.map +1 -1
- package/dist/shared-vocabulary.d.ts.map +1 -1
- package/dist/shared-vocabulary.js +4 -3
- package/dist/shared-vocabulary.js.map +1 -1
- package/dist/support-evidence.d.ts +22 -0
- package/dist/support-evidence.d.ts.map +1 -0
- package/dist/support-evidence.js +93 -0
- package/dist/support-evidence.js.map +1 -0
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +38 -47
- package/dist/support.js.map +1 -1
- package/dist/trusted-item-session.d.ts +49 -0
- package/dist/trusted-item-session.d.ts.map +1 -0
- package/dist/trusted-item-session.js +261 -0
- package/dist/trusted-item-session.js.map +1 -0
- package/dist/tts.d.ts.map +1 -1
- package/dist/tts.js +16 -2
- package/dist/tts.js.map +1 -1
- package/dist/validation-geometry.d.ts +8 -0
- package/dist/validation-geometry.d.ts.map +1 -0
- package/dist/validation-geometry.js +187 -0
- package/dist/validation-geometry.js.map +1 -0
- package/dist/validation-interactions.d.ts +3 -0
- package/dist/validation-interactions.d.ts.map +1 -0
- package/dist/validation-interactions.js +644 -0
- package/dist/validation-interactions.js.map +1 -0
- package/dist/validation-primitives.d.ts +11 -0
- package/dist/validation-primitives.d.ts.map +1 -0
- package/dist/validation-primitives.js +48 -0
- package/dist/validation-primitives.js.map +1 -0
- package/dist/validation-processing.d.ts +4 -0
- package/dist/validation-processing.d.ts.map +1 -0
- package/dist/validation-processing.js +569 -0
- package/dist/validation-processing.js.map +1 -0
- package/dist/validation-shared-vocabulary-layout.d.ts +10 -0
- package/dist/validation-shared-vocabulary-layout.d.ts.map +1 -0
- package/dist/validation-shared-vocabulary-layout.js +102 -0
- package/dist/validation-shared-vocabulary-layout.js.map +1 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +27 -1641
- package/dist/validation.js.map +1 -1
- package/dist/xml.js +1 -1
- package/dist/xml.js.map +1 -1
- package/package.json +4 -2
- package/src/adaptive-turn-materializer.ts +85 -0
- package/src/adaptive-turn.ts +109 -0
- package/src/assert-never.ts +4 -0
- package/src/attempt-state-constants.ts +5 -0
- package/src/attempt-state.ts +245 -0
- package/src/catalog.ts +1 -1
- package/src/content-text.ts +3 -0
- package/src/custom-operators.ts +12 -0
- package/src/delivery-redaction.ts +268 -0
- package/src/delivery-security.ts +59 -245
- package/src/index.ts +9 -0
- package/src/parser-custom-interactions.ts +1 -1
- package/src/parser-declarations.ts +4 -3
- package/src/parser-item-metadata.ts +8 -7
- package/src/parser-processing.ts +2 -2
- package/src/parser-values.ts +36 -0
- package/src/processing-evaluator-boolean.ts +48 -0
- package/src/processing-evaluator-collection.ts +50 -0
- package/src/processing-evaluator-comparison.ts +106 -0
- package/src/processing-evaluator-custom.ts +20 -0
- package/src/processing-evaluator-geometry.ts +26 -0
- package/src/processing-evaluator-numeric.ts +155 -0
- package/src/processing-evaluator-string.ts +60 -0
- package/src/processing-evaluator-variable.ts +81 -0
- package/src/processing-evaluator.ts +173 -0
- package/src/processing-expression-children.ts +69 -0
- package/src/processing-mapping.ts +153 -0
- package/src/processing-operators.ts +184 -0
- package/src/processing-random.ts +16 -0
- package/src/processing-state.ts +69 -0
- package/src/processing-templates.ts +12 -0
- package/src/processing-values.ts +162 -0
- package/src/processing-variables.ts +83 -0
- package/src/serializer-processing-expressions.ts +2 -12
- package/src/serializer-processing-xml.ts +2 -2
- package/src/serializer-processing.ts +3 -0
- package/src/server-scoring.ts +30 -226
- package/src/session.ts +128 -2006
- package/src/shared-vocabulary-coverage-policy.ts +1 -1
- package/src/shared-vocabulary-generated-families.ts +1 -1
- package/src/shared-vocabulary-support.ts +3 -3
- package/src/shared-vocabulary.ts +3 -2
- package/src/support-evidence.ts +107 -0
- package/src/support.ts +68 -54
- package/src/trusted-item-session.ts +429 -0
- package/src/tts.ts +29 -15
- package/src/validation-geometry.ts +204 -0
- package/src/validation-interactions.ts +831 -0
- package/src/validation-primitives.ts +63 -0
- package/src/validation-processing.ts +785 -0
- package/src/validation-shared-vocabulary-layout.ts +117 -0
- package/src/validation.ts +44 -2077
- package/src/xml.ts +1 -1
- package/src/interaction-test-fixtures.ts +0 -44
- package/src/serializer-processing.fixtures.ts +0 -469
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type UnaryExpressionType,
|
|
12
12
|
} from "./processing-expression-tags.js";
|
|
13
13
|
import { escapeXmlText } from "./xml.js";
|
|
14
|
+
import { assertNever } from "./assert-never.js";
|
|
14
15
|
import {
|
|
15
16
|
addSerializerDiagnostic,
|
|
16
17
|
knownAttributesWithBagFallback,
|
|
@@ -274,7 +275,7 @@ export function serializeExpression(
|
|
|
274
275
|
indent,
|
|
275
276
|
);
|
|
276
277
|
default:
|
|
277
|
-
return
|
|
278
|
+
return assertNever(expression);
|
|
278
279
|
}
|
|
279
280
|
}
|
|
280
281
|
|
|
@@ -443,14 +444,3 @@ function isSerializableBaseValue(value: QtiValue): value is string | number | bo
|
|
|
443
444
|
(typeof value === "number" && Number.isFinite(value))
|
|
444
445
|
);
|
|
445
446
|
}
|
|
446
|
-
|
|
447
|
-
function unsupportedExpression(
|
|
448
|
-
expression: QtiProcessingExpression,
|
|
449
|
-
context: SerializationContext,
|
|
450
|
-
): string[] {
|
|
451
|
-
addSerializerDiagnostic(context, "responseProcessing.serialize.unsupportedExpression", {
|
|
452
|
-
message: `Unsupported response-processing expression type: ${expression.type}.`,
|
|
453
|
-
source: expression.source,
|
|
454
|
-
});
|
|
455
|
-
return [];
|
|
456
|
-
}
|
|
@@ -41,13 +41,13 @@ export function knownAttributesWithBagFallback(
|
|
|
41
41
|
});
|
|
42
42
|
const extras = Object.entries(bag ?? {})
|
|
43
43
|
.filter(([name]) => !knownNames.has(name))
|
|
44
|
-
.
|
|
44
|
+
.toSorted(([left], [right]) => left.localeCompare(right));
|
|
45
45
|
return [...resolved, ...extras];
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export function sortedBagAttributes(bag: Record<string, string> | undefined): XmlAttribute[] {
|
|
49
49
|
return Object.entries(bag ?? {})
|
|
50
|
-
.
|
|
50
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
51
51
|
.map(([name, value]) => [name, value] as const);
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
QtiResponseProcessing,
|
|
14
14
|
QtiResponseRule,
|
|
15
15
|
} from "./types.js";
|
|
16
|
+
import { assertNever } from "./assert-never.js";
|
|
16
17
|
|
|
17
18
|
export interface QtiSerializeResponseProcessingResult {
|
|
18
19
|
ok: boolean;
|
|
@@ -114,6 +115,8 @@ function serializeResponseRule(
|
|
|
114
115
|
rule.rules.flatMap((child) => serializeResponseRule(child, context, indent + 1)),
|
|
115
116
|
indent,
|
|
116
117
|
);
|
|
118
|
+
default:
|
|
119
|
+
return assertNever(rule);
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
|
package/src/server-scoring.ts
CHANGED
|
@@ -1,32 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export interface QtiServerScoringResponseInput {
|
|
15
|
-
identifier: string;
|
|
16
|
-
value: unknown;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface QtiServerScoringInput {
|
|
1
|
+
import type { QtiAttemptStateV1, QtiAttemptStatus, QtiDiagnostic, QtiValue } from "./types.js";
|
|
2
|
+
import {
|
|
3
|
+
runTrustedItemSession,
|
|
4
|
+
type QtiTrustedItemParseOptions,
|
|
5
|
+
type QtiTrustedResponseApplication,
|
|
6
|
+
type QtiTrustedResponseInput,
|
|
7
|
+
} from "./trusted-item-session.js";
|
|
8
|
+
|
|
9
|
+
export type QtiServerScoringResponseInput = QtiTrustedResponseInput;
|
|
10
|
+
|
|
11
|
+
export interface QtiServerScoringInput
|
|
12
|
+
extends QtiTrustedResponseApplication, QtiTrustedItemParseOptions {
|
|
20
13
|
itemXml: string;
|
|
21
|
-
/**
|
|
22
|
-
* Server-trusted response values. This API validates JSON/QTI value shape and declared
|
|
23
|
-
* identifiers, but it does not run candidate response-validation policy such as required
|
|
24
|
-
* interactions or min/max response counts.
|
|
25
|
-
*/
|
|
26
|
-
trustedResponses?: Record<string, unknown> | readonly QtiServerScoringResponseInput[] | undefined;
|
|
27
|
-
trustedInteractionStates?: Record<string, QtiPortableCustomStateValue> | undefined;
|
|
28
14
|
status?: QtiAttemptStatus | undefined;
|
|
29
|
-
allowedUndeclaredResponseIdentifiers?: readonly string[] | undefined;
|
|
30
15
|
}
|
|
31
16
|
|
|
32
17
|
export interface QtiServerScoringResult {
|
|
@@ -39,206 +24,25 @@ export interface QtiServerScoringResult {
|
|
|
39
24
|
}
|
|
40
25
|
|
|
41
26
|
export function scoreQtiItemServerSide(input: QtiServerScoringInput): QtiServerScoringResult {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const allowedUndeclaredResponseIdentifiers = new Set(
|
|
56
|
-
input.allowedUndeclaredResponseIdentifiers ?? [],
|
|
57
|
-
);
|
|
58
|
-
const parseDiagnostics = parsed.diagnostics.filter(
|
|
59
|
-
(diagnostic) =>
|
|
60
|
-
!isAllowedUndeclaredVariableReference(diagnostic, allowedUndeclaredResponseIdentifiers),
|
|
61
|
-
);
|
|
62
|
-
if (!parsed.document || parseDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
63
|
-
return failed(parseDiagnostics);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const diagnostics = [...parseDiagnostics];
|
|
67
|
-
const session = createItemSession(parsed.document);
|
|
68
|
-
if (input.status) session.setStatus(input.status);
|
|
69
|
-
|
|
70
|
-
const responseIdentifiers = new Set(
|
|
71
|
-
parsed.document.item.responseDeclarations.map((declaration) => declaration.identifier),
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
for (const response of normalizeResponseInputs(input.trustedResponses)) {
|
|
75
|
-
const identifier = response.identifier.trim();
|
|
76
|
-
if (!identifier) {
|
|
77
|
-
diagnostics.push({
|
|
78
|
-
code: "serverScoring.response.identifier",
|
|
79
|
-
severity: "error",
|
|
80
|
-
message: "Trusted response identifiers must be non-empty strings.",
|
|
81
|
-
});
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
!responseIdentifiers.has(identifier) &&
|
|
87
|
-
!allowedUndeclaredResponseIdentifiers.has(identifier)
|
|
88
|
-
) {
|
|
89
|
-
diagnostics.push({
|
|
90
|
-
code: "serverScoring.response.ignored",
|
|
91
|
-
severity: "warning",
|
|
92
|
-
message: `Trusted response ${identifier} was ignored because it is not declared by the item.`,
|
|
93
|
-
});
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const value = readQtiJsonValue(response.value);
|
|
98
|
-
if (value === undefined) {
|
|
99
|
-
diagnostics.push({
|
|
100
|
-
code: "serverScoring.response.value",
|
|
101
|
-
severity: "error",
|
|
102
|
-
message: `Trusted response ${identifier} is not a supported QTI value.`,
|
|
103
|
-
});
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
session.respond(identifier, value);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
for (const [identifier, state] of Object.entries(input.trustedInteractionStates ?? {})) {
|
|
110
|
-
if (!isQtiPortableCustomStateValue(state)) {
|
|
111
|
-
diagnostics.push({
|
|
112
|
-
code: "serverScoring.interactionState.value",
|
|
113
|
-
severity: "error",
|
|
114
|
-
message: `Trusted interaction state ${identifier} is not a supported portable custom state value.`,
|
|
115
|
-
});
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
session.setInteractionState(identifier, state);
|
|
121
|
-
} catch (error) {
|
|
122
|
-
diagnostics.push({
|
|
123
|
-
code: "serverScoring.interactionState.identifier",
|
|
124
|
-
severity: "error",
|
|
125
|
-
message: error instanceof Error ? error.message : String(error),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
131
|
-
return failed(diagnostics);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let scored: QtiScoreResult;
|
|
135
|
-
try {
|
|
136
|
-
scored = session.score();
|
|
137
|
-
} catch (error) {
|
|
138
|
-
return failed([
|
|
139
|
-
...diagnostics,
|
|
140
|
-
{
|
|
141
|
-
code: "serverScoring.score.exception",
|
|
142
|
-
severity: "error",
|
|
143
|
-
message: error instanceof Error ? error.message : String(error),
|
|
144
|
-
},
|
|
145
|
-
]);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const scoredDiagnostics = [...diagnostics, ...scored.diagnostics];
|
|
149
|
-
const score = readNumericScore(scored.outcomes.SCORE);
|
|
150
|
-
const scoredState = stripUndeclaredResponses(scored.state, responseIdentifiers);
|
|
151
|
-
if (scored.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
152
|
-
return failed(scoredDiagnostics, scoredState, scored.outcomes, score);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (shouldRequireNumericScore(parsed.document.item) && score === null) {
|
|
156
|
-
return failed(
|
|
157
|
-
[
|
|
158
|
-
...scoredDiagnostics,
|
|
159
|
-
{
|
|
160
|
-
code: "serverScoring.score.missing",
|
|
161
|
-
severity: "error",
|
|
162
|
-
message: "Server-side scoring did not produce a numeric SCORE outcome.",
|
|
163
|
-
},
|
|
164
|
-
],
|
|
165
|
-
scoredState,
|
|
166
|
-
scored.outcomes,
|
|
167
|
-
score,
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
ok: true,
|
|
173
|
-
diagnostics: scoredDiagnostics,
|
|
174
|
-
state: scoredState,
|
|
175
|
-
responses: scoredState.responses,
|
|
176
|
-
outcomes: scored.outcomes,
|
|
177
|
-
score,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function normalizeResponseInputs(
|
|
182
|
-
responses: QtiServerScoringInput["trustedResponses"],
|
|
183
|
-
): QtiServerScoringResponseInput[] {
|
|
184
|
-
if (!responses) return [];
|
|
185
|
-
if (Array.isArray(responses)) return [...responses];
|
|
186
|
-
return Object.entries(responses).map(([identifier, value]) => ({ identifier, value }));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function shouldRequireNumericScore(item: QtiAssessmentItem): boolean {
|
|
190
|
-
return (
|
|
191
|
-
Boolean(item.responseProcessing) ||
|
|
192
|
-
item.outcomeDeclarations.some((declaration) => declaration.identifier === "SCORE")
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function readNumericScore(value: QtiValue | undefined): number | null {
|
|
197
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
198
|
-
if (typeof value === "string") {
|
|
199
|
-
const score = Number(value.trim());
|
|
200
|
-
return Number.isFinite(score) ? score : null;
|
|
201
|
-
}
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function isAllowedUndeclaredVariableReference(
|
|
206
|
-
diagnostic: QtiDiagnostic,
|
|
207
|
-
allowedUndeclaredResponseIdentifiers: Set<string>,
|
|
208
|
-
): boolean {
|
|
209
|
-
if (diagnostic.code !== "processing.variable.reference") return false;
|
|
210
|
-
const identifier = diagnostic.message.match(
|
|
211
|
-
/^Processing expression references missing variable (.+)\.$/,
|
|
212
|
-
)?.[1];
|
|
213
|
-
return Boolean(identifier && allowedUndeclaredResponseIdentifiers.has(identifier));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function failed(
|
|
217
|
-
diagnostics: QtiDiagnostic[],
|
|
218
|
-
state: QtiAttemptStateV1 | null = null,
|
|
219
|
-
outcomes: Record<string, QtiValue> = {},
|
|
220
|
-
score: number | null = null,
|
|
221
|
-
): QtiServerScoringResult {
|
|
222
|
-
return {
|
|
223
|
-
ok: false,
|
|
224
|
-
diagnostics,
|
|
225
|
-
state,
|
|
226
|
-
responses: state?.responses ?? {},
|
|
227
|
-
outcomes,
|
|
228
|
-
score,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
27
|
+
const result = runTrustedItemSession({
|
|
28
|
+
itemXml: input.itemXml,
|
|
29
|
+
diagnosticPrefix: "serverScoring",
|
|
30
|
+
allowedUndeclaredResponseIdentifiers: input.allowedUndeclaredResponseIdentifiers,
|
|
31
|
+
submission: {
|
|
32
|
+
trustedResponses: input.trustedResponses,
|
|
33
|
+
trustedInteractionStates: input.trustedInteractionStates,
|
|
34
|
+
},
|
|
35
|
+
attemptStatus: input.status,
|
|
36
|
+
scoring: "always",
|
|
37
|
+
requireNumericScore: true,
|
|
38
|
+
});
|
|
231
39
|
|
|
232
|
-
function stripUndeclaredResponses(
|
|
233
|
-
state: QtiAttemptStateV1,
|
|
234
|
-
declaredResponseIdentifiers: Set<string>,
|
|
235
|
-
): QtiAttemptStateV1 {
|
|
236
40
|
return {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
41
|
+
ok: result.ok,
|
|
42
|
+
diagnostics: result.diagnostics,
|
|
43
|
+
state: result.state,
|
|
44
|
+
responses: result.responses,
|
|
45
|
+
outcomes: result.outcomes,
|
|
46
|
+
score: result.score,
|
|
243
47
|
};
|
|
244
48
|
}
|