@longsightgroup/qti3-core 0.10.5 → 0.11.0
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 +153 -4
- package/dist/adaptive-turn-materializer.js +4 -5
- package/dist/adaptive-turn-materializer.js.map +1 -1
- package/dist/adaptive-turn.d.ts.map +1 -1
- package/dist/adaptive-turn.js +1 -0
- package/dist/adaptive-turn.js.map +1 -1
- package/dist/attempt-state.d.ts.map +1 -1
- package/dist/attempt-state.js +41 -1
- package/dist/attempt-state.js.map +1 -1
- package/dist/catalog.d.ts +8 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +10 -0
- package/dist/catalog.js.map +1 -1
- package/dist/diagnostics.d.ts +13 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +28 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/item-scoring-expectation.d.ts +2 -0
- package/dist/item-scoring-expectation.d.ts.map +1 -1
- package/dist/item-scoring-expectation.js +8 -0
- package/dist/item-scoring-expectation.js.map +1 -1
- package/dist/item-submission-materialization.d.ts.map +1 -1
- package/dist/item-submission-materialization.js +2 -0
- package/dist/item-submission-materialization.js.map +1 -1
- package/dist/parser-processing.d.ts.map +1 -1
- package/dist/parser-processing.js +8 -18
- package/dist/parser-processing.js.map +1 -1
- package/dist/parser-values.d.ts.map +1 -1
- package/dist/parser-values.js +1 -3
- package/dist/parser-values.js.map +1 -1
- package/dist/parser.js +36 -6
- package/dist/parser.js.map +1 -1
- package/dist/processing-equality.d.ts +10 -0
- package/dist/processing-equality.d.ts.map +1 -0
- package/dist/processing-equality.js +78 -0
- package/dist/processing-equality.js.map +1 -0
- package/dist/processing-evaluator-comparison.d.ts.map +1 -1
- package/dist/processing-evaluator-comparison.js +14 -9
- package/dist/processing-evaluator-comparison.js.map +1 -1
- package/dist/processing-evaluator-numeric.d.ts.map +1 -1
- package/dist/processing-evaluator-numeric.js +2 -1
- package/dist/processing-evaluator-numeric.js.map +1 -1
- package/dist/processing-evaluator-string.d.ts.map +1 -1
- package/dist/processing-evaluator-string.js +26 -10
- package/dist/processing-evaluator-string.js.map +1 -1
- package/dist/processing-evaluator-variable.d.ts.map +1 -1
- package/dist/processing-evaluator-variable.js +9 -3
- package/dist/processing-evaluator-variable.js.map +1 -1
- package/dist/processing-evaluator.d.ts +5 -1
- package/dist/processing-evaluator.d.ts.map +1 -1
- package/dist/processing-evaluator.js +13 -4
- package/dist/processing-evaluator.js.map +1 -1
- package/dist/processing-expression-children.d.ts.map +1 -1
- package/dist/processing-expression-children.js +2 -3
- package/dist/processing-expression-children.js.map +1 -1
- package/dist/processing-expression-tags.d.ts +2 -1
- package/dist/processing-expression-tags.d.ts.map +1 -1
- package/dist/processing-expression-tags.js +2 -1
- package/dist/processing-expression-tags.js.map +1 -1
- package/dist/processing-mapping.d.ts.map +1 -1
- package/dist/processing-mapping.js +26 -16
- package/dist/processing-mapping.js.map +1 -1
- package/dist/processing-state.d.ts +2 -2
- package/dist/processing-state.d.ts.map +1 -1
- package/dist/processing-state.js +5 -2
- package/dist/processing-state.js.map +1 -1
- package/dist/processing-values.d.ts +1 -2
- package/dist/processing-values.d.ts.map +1 -1
- package/dist/processing-values.js +1 -3
- package/dist/processing-values.js.map +1 -1
- package/dist/processing-variables.d.ts.map +1 -1
- package/dist/processing-variables.js +10 -5
- package/dist/processing-variables.js.map +1 -1
- package/dist/qti-package-assets.d.ts.map +1 -1
- package/dist/qti-package-assets.js +16 -10
- package/dist/qti-package-assets.js.map +1 -1
- package/dist/qti-package-manifest.d.ts +1 -0
- package/dist/qti-package-manifest.d.ts.map +1 -1
- package/dist/qti-package-manifest.js +1 -0
- package/dist/qti-package-manifest.js.map +1 -1
- package/dist/qti-package-metadata.d.ts.map +1 -1
- package/dist/qti-package-metadata.js +11 -0
- package/dist/qti-package-metadata.js.map +1 -1
- package/dist/qti-package-plan.d.ts +1 -1
- package/dist/qti-package-plan.d.ts.map +1 -1
- package/dist/qti-package-types.d.ts +9 -0
- package/dist/qti-package-types.d.ts.map +1 -1
- package/dist/qti-package-xml.d.ts +6 -1
- package/dist/qti-package-xml.d.ts.map +1 -1
- package/dist/qti-package-xml.js +11 -6
- package/dist/qti-package-xml.js.map +1 -1
- package/dist/qti-package-zip.d.ts +13 -1
- package/dist/qti-package-zip.d.ts.map +1 -1
- package/dist/qti-package-zip.js +185 -54
- package/dist/qti-package-zip.js.map +1 -1
- package/dist/qti-package.d.ts +12 -5
- package/dist/qti-package.d.ts.map +1 -1
- package/dist/qti-package.js +63 -8
- package/dist/qti-package.js.map +1 -1
- package/dist/response-validation-policy.d.ts +3 -1
- package/dist/response-validation-policy.d.ts.map +1 -1
- package/dist/response-validation-policy.js +66 -6
- package/dist/response-validation-policy.js.map +1 -1
- package/dist/response-validation.d.ts +3 -1
- package/dist/response-validation.d.ts.map +1 -1
- package/dist/response-validation.js +32 -10
- package/dist/response-validation.js.map +1 -1
- package/dist/scoring-disposition-policy.d.ts.map +1 -1
- package/dist/scoring-disposition-policy.js +6 -2
- package/dist/scoring-disposition-policy.js.map +1 -1
- package/dist/serializer-processing-expressions.d.ts.map +1 -1
- package/dist/serializer-processing-expressions.js +2 -10
- package/dist/serializer-processing-expressions.js.map +1 -1
- package/dist/server-scoring.d.ts.map +1 -1
- package/dist/server-scoring.js +1 -0
- package/dist/server-scoring.js.map +1 -1
- package/dist/session-builtins.d.ts +16 -0
- package/dist/session-builtins.d.ts.map +1 -0
- package/dist/session-builtins.js +58 -0
- package/dist/session-builtins.js.map +1 -0
- package/dist/session.d.ts +5 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +63 -11
- package/dist/session.js.map +1 -1
- package/dist/support-evidence.d.ts.map +1 -1
- package/dist/support-evidence.js +16 -3
- package/dist/support-evidence.js.map +1 -1
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +72 -24
- package/dist/support.js.map +1 -1
- package/dist/text-response.d.ts +9 -0
- package/dist/text-response.d.ts.map +1 -0
- package/dist/text-response.js +102 -0
- package/dist/text-response.js.map +1 -0
- package/dist/trusted-item-session.d.ts +4 -1
- package/dist/trusted-item-session.d.ts.map +1 -1
- package/dist/trusted-item-session.js +12 -6
- package/dist/trusted-item-session.js.map +1 -1
- package/dist/types.d.ts +23 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/validation-declaration-entries.d.ts.map +1 -1
- package/dist/validation-declaration-entries.js +11 -1
- package/dist/validation-declaration-entries.js.map +1 -1
- package/dist/validation-interactions.d.ts.map +1 -1
- package/dist/validation-interactions.js +102 -12
- package/dist/validation-interactions.js.map +1 -1
- package/dist/validation-item-children.d.ts +4 -0
- package/dist/validation-item-children.d.ts.map +1 -0
- package/dist/validation-item-children.js +45 -0
- package/dist/validation-item-children.js.map +1 -0
- package/dist/validation-processing.d.ts.map +1 -1
- package/dist/validation-processing.js +45 -12
- package/dist/validation-processing.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +12 -0
- package/dist/validation.js.map +1 -1
- package/dist/value-guards.d.ts +4 -0
- package/dist/value-guards.d.ts.map +1 -0
- package/dist/value-guards.js +5 -0
- package/dist/value-guards.js.map +1 -0
- package/dist/xml-schema-regex-data.d.ts +48 -0
- package/dist/xml-schema-regex-data.d.ts.map +1 -0
- package/dist/xml-schema-regex-data.js +403 -0
- package/dist/xml-schema-regex-data.js.map +1 -0
- package/dist/xml-schema-regex-parser.d.ts +31 -0
- package/dist/xml-schema-regex-parser.d.ts.map +1 -0
- package/dist/xml-schema-regex-parser.js +245 -0
- package/dist/xml-schema-regex-parser.js.map +1 -0
- package/dist/xml-schema-regex.d.ts +14 -0
- package/dist/xml-schema-regex.d.ts.map +1 -0
- package/dist/xml-schema-regex.js +114 -0
- package/dist/xml-schema-regex.js.map +1 -0
- package/package.json +1 -1
- package/src/adaptive-turn-materializer.ts +4 -3
- package/src/adaptive-turn.ts +1 -0
- package/src/attempt-state.ts +54 -1
- package/src/catalog.ts +29 -0
- package/src/diagnostics.ts +29 -0
- package/src/index.ts +16 -0
- package/src/item-scoring-expectation.ts +8 -0
- package/src/item-submission-materialization.ts +2 -0
- package/src/parser-processing.ts +15 -18
- package/src/parser-values.ts +1 -5
- package/src/parser.ts +38 -6
- package/src/processing-equality.ts +104 -0
- package/src/processing-evaluator-comparison.ts +19 -14
- package/src/processing-evaluator-numeric.ts +2 -6
- package/src/processing-evaluator-string.ts +25 -18
- package/src/processing-evaluator-variable.ts +8 -3
- package/src/processing-evaluator.ts +20 -7
- package/src/processing-expression-children.ts +2 -3
- package/src/processing-expression-tags.ts +2 -1
- package/src/processing-mapping.ts +31 -25
- package/src/processing-state.ts +6 -1
- package/src/processing-values.ts +2 -11
- package/src/processing-variables.ts +9 -7
- package/src/qti-package-assets.ts +20 -13
- package/src/qti-package-manifest.ts +1 -0
- package/src/qti-package-metadata.ts +11 -0
- package/src/qti-package-plan.ts +1 -1
- package/src/qti-package-types.ts +10 -0
- package/src/qti-package-xml.ts +11 -6
- package/src/qti-package-zip.ts +330 -97
- package/src/qti-package.ts +106 -7
- package/src/response-validation-policy.ts +76 -4
- package/src/response-validation.ts +51 -5
- package/src/scoring-disposition-policy.ts +7 -2
- package/src/serializer-processing-expressions.ts +8 -25
- package/src/server-scoring.ts +1 -0
- package/src/session-builtins.ts +75 -0
- package/src/session.ts +64 -8
- package/src/support-evidence.ts +16 -3
- package/src/support.ts +94 -24
- package/src/text-response.ts +106 -0
- package/src/trusted-item-session.ts +17 -5
- package/src/types.ts +32 -4
- package/src/validation-declaration-entries.ts +11 -1
- package/src/validation-interactions.ts +139 -14
- package/src/validation-item-children.ts +48 -0
- package/src/validation-processing.ts +47 -10
- package/src/validation.ts +14 -0
- package/src/value-guards.ts +6 -0
- package/src/xml-schema-regex-data.ts +404 -0
- package/src/xml-schema-regex-parser.ts +234 -0
- package/src/xml-schema-regex.ts +115 -0
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { QtiAssessmentItem } from "./types.js";
|
|
2
2
|
|
|
3
|
+
/** An external score cannot be inferred from an initialized or restored outcome value. */
|
|
4
|
+
export function itemHasExternalScore(item: QtiAssessmentItem): boolean {
|
|
5
|
+
const scoring = item.outcomeDeclarations.find((declaration) => declaration.identifier === "SCORE")
|
|
6
|
+
?.attributes["external-scored"];
|
|
7
|
+
return scoring === "human" || scoring === "externalMachine";
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
/** Items that declare automated scoring via response processing or a SCORE outcome. */
|
|
4
11
|
export function itemExpectsAutomatedScore(item: QtiAssessmentItem): boolean {
|
|
12
|
+
if (itemHasExternalScore(item)) return false;
|
|
5
13
|
return (
|
|
6
14
|
Boolean(item.responseProcessing) ||
|
|
7
15
|
item.outcomeDeclarations.some((declaration) => declaration.identifier === "SCORE")
|
|
@@ -65,6 +65,7 @@ export function materializeQtiItemSubmission(
|
|
|
65
65
|
parsedItem: parsedResult.parsed,
|
|
66
66
|
diagnosticPrefix: "itemSubmission",
|
|
67
67
|
allowedUndeclaredResponseIdentifiers: input.allowedUndeclaredResponseIdentifiers,
|
|
68
|
+
sessionEnvironment: input.sessionEnvironment,
|
|
68
69
|
priorState: priorState.state,
|
|
69
70
|
submission: {
|
|
70
71
|
trustedResponses: input.trustedResponses,
|
|
@@ -98,6 +99,7 @@ function materializeAdaptiveSubmission(
|
|
|
98
99
|
parsedItem,
|
|
99
100
|
diagnosticPrefix: "itemSubmission",
|
|
100
101
|
allowedUndeclaredResponseIdentifiers: input.allowedUndeclaredResponseIdentifiers,
|
|
102
|
+
sessionEnvironment: input.sessionEnvironment,
|
|
101
103
|
priorState: input.existingState,
|
|
102
104
|
trustedResponses: input.trustedResponses,
|
|
103
105
|
trustedInteractionStates: input.trustedInteractionStates,
|
package/src/parser-processing.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
coerceValue,
|
|
3
|
+
parseCoords,
|
|
4
|
+
parseInteger,
|
|
5
|
+
parseShape,
|
|
6
|
+
parseXmlBoolean,
|
|
7
|
+
} from "./parser-values.js";
|
|
2
8
|
import { responseConditionsFromRules } from "./processing-rules.js";
|
|
3
9
|
import type {
|
|
4
10
|
QtiLookupOutcomeValue,
|
|
@@ -215,12 +221,8 @@ function parseExpression(node: XmlNode): QtiProcessingExpression | undefined {
|
|
|
215
221
|
}
|
|
216
222
|
|
|
217
223
|
if (node.localName === "qti-is-null") {
|
|
218
|
-
const
|
|
219
|
-
return {
|
|
220
|
-
type: "isNull",
|
|
221
|
-
identifier: variable?.attributes.identifier ?? "",
|
|
222
|
-
source: node.source,
|
|
223
|
-
};
|
|
224
|
+
const expression = parseFirstExpression(node);
|
|
225
|
+
return expression ? { type: "isNull", expression, source: node.source } : undefined;
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
if (node.localName === "qti-map-response") {
|
|
@@ -281,14 +283,8 @@ function parseExpression(node: XmlNode): QtiProcessingExpression | undefined {
|
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
if (node.localName === "qti-random") {
|
|
284
|
-
const
|
|
285
|
-
return {
|
|
286
|
-
type: "random",
|
|
287
|
-
values: childElements(multiple ?? node)
|
|
288
|
-
.map(parseExpression)
|
|
289
|
-
.filter((expression): expression is QtiProcessingExpression => expression !== undefined),
|
|
290
|
-
source: node.source,
|
|
291
|
-
};
|
|
286
|
+
const expression = parseFirstExpression(node);
|
|
287
|
+
return expression ? { type: "random", expression, source: node.source } : undefined;
|
|
292
288
|
}
|
|
293
289
|
|
|
294
290
|
if (node.localName === "qti-multiple") {
|
|
@@ -473,15 +469,16 @@ function parseExpression(node: XmlNode): QtiProcessingExpression | undefined {
|
|
|
473
469
|
const [left, right] = childElements(node)
|
|
474
470
|
.map(parseExpression)
|
|
475
471
|
.filter((expression): expression is QtiProcessingExpression => expression !== undefined);
|
|
476
|
-
if (left && right)
|
|
472
|
+
if (left && right)
|
|
473
|
+
return { type: "equal", left, right, attributes: node.attributes, source: node.source };
|
|
477
474
|
}
|
|
478
475
|
|
|
479
476
|
if (node.localName === "qti-equal-rounded") {
|
|
480
477
|
const [left, right] = childElements(node)
|
|
481
478
|
.map(parseExpression)
|
|
482
479
|
.filter((expression): expression is QtiProcessingExpression => expression !== undefined);
|
|
483
|
-
const roundingMode = node.attributes["rounding-mode"] ?? "";
|
|
484
|
-
const figures =
|
|
480
|
+
const roundingMode = node.attributes["rounding-mode"] ?? "significantFigures";
|
|
481
|
+
const figures = parseInteger(node.attributes.figures) ?? node.attributes.figures ?? "";
|
|
485
482
|
if (left && right) {
|
|
486
483
|
return { type: "equalRounded", left, right, roundingMode, figures, source: node.source };
|
|
487
484
|
}
|
package/src/parser-values.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
QtiBaseType,
|
|
3
3
|
QtiCardinality,
|
|
4
|
-
QtiRecordValue,
|
|
5
4
|
QtiResponseDeclaration,
|
|
6
5
|
QtiScalarValue,
|
|
7
6
|
QtiValue,
|
|
8
7
|
} from "./types.js";
|
|
8
|
+
import { isRecordValue } from "./value-guards.js";
|
|
9
9
|
|
|
10
10
|
const QTI_BASE_TYPES = new Set<string>([
|
|
11
11
|
"identifier",
|
|
@@ -127,7 +127,3 @@ export function parseCoords(value: string | undefined): number[] {
|
|
|
127
127
|
.map((part) => Number(part.trim()))
|
|
128
128
|
.filter((part) => Number.isFinite(part));
|
|
129
129
|
}
|
|
130
|
-
|
|
131
|
-
function isRecordValue(value: QtiValue): value is QtiRecordValue {
|
|
132
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
|
-
}
|
package/src/parser.ts
CHANGED
|
@@ -178,6 +178,10 @@ function parseAssessmentItem(node: XmlNode, diagnostics: QtiDiagnostic[]): QtiAs
|
|
|
178
178
|
|
|
179
179
|
return {
|
|
180
180
|
identifier,
|
|
181
|
+
sourceChildren: node.children.map((child) => ({
|
|
182
|
+
qtiName: child.localName,
|
|
183
|
+
source: child.source,
|
|
184
|
+
})),
|
|
181
185
|
title: node.attributes.title,
|
|
182
186
|
language: node.attributes["xml:lang"] ?? node.attributes.lang,
|
|
183
187
|
adaptive: parseXmlBoolean(node.attributes.adaptive) === true,
|
|
@@ -213,6 +217,36 @@ function diagnoseProcessingElements(
|
|
|
213
217
|
if (!processingNode) return;
|
|
214
218
|
for (const node of [processingNode, ...descendants(processingNode, () => true)]) {
|
|
215
219
|
if (!node.localName.startsWith("qti-")) continue;
|
|
220
|
+
if (
|
|
221
|
+
node.localName === "qti-pattern-match" &&
|
|
222
|
+
(node.attributes.pattern === undefined || childElements(node).length !== 1)
|
|
223
|
+
) {
|
|
224
|
+
diagnostics.push({
|
|
225
|
+
code: "processing.pattern.required",
|
|
226
|
+
severity: "error",
|
|
227
|
+
message: "qti-pattern-match requires a pattern attribute and exactly one expression.",
|
|
228
|
+
path: node.source.path,
|
|
229
|
+
source: node.source,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (node.localName === "qti-is-null" && childElements(node).length !== 1) {
|
|
233
|
+
diagnostics.push({
|
|
234
|
+
code: "processing.isNull.arity",
|
|
235
|
+
severity: "error",
|
|
236
|
+
message: "qti-is-null requires exactly one expression.",
|
|
237
|
+
path: node.source.path,
|
|
238
|
+
source: node.source,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (node.localName === "qti-random" && childElements(node).length !== 1) {
|
|
242
|
+
diagnostics.push({
|
|
243
|
+
code: "processing.random.arity",
|
|
244
|
+
severity: "error",
|
|
245
|
+
message: "qti-random requires exactly one container expression.",
|
|
246
|
+
path: node.source.path,
|
|
247
|
+
source: node.source,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
216
250
|
if (
|
|
217
251
|
processingNode.localName === "qti-response-processing" &&
|
|
218
252
|
responseProcessingForbiddenNames.has(node.localName)
|
|
@@ -518,6 +552,7 @@ function parseObjectAsset(node: XmlNode | undefined): QtiObjectAsset | undefined
|
|
|
518
552
|
const inferredSvgDimensions = inlineSvgDimensions(data);
|
|
519
553
|
return {
|
|
520
554
|
data,
|
|
555
|
+
imageAttributes: node.localName === "img" ? node.attributes : pictureImage?.attributes,
|
|
521
556
|
type:
|
|
522
557
|
node.attributes.type ??
|
|
523
558
|
pictureImage?.attributes.type ??
|
|
@@ -655,10 +690,7 @@ function firstSourceType(sources: QtiMediaSource[]): string | undefined {
|
|
|
655
690
|
}
|
|
656
691
|
|
|
657
692
|
function firstSrcsetCandidate(srcset: string | undefined): string | undefined {
|
|
658
|
-
return srcset
|
|
659
|
-
?.split(",")
|
|
660
|
-
.map((candidate) => candidate.trim().split(/\s+/)[0])
|
|
661
|
-
.find((candidate) => candidate && candidate.length > 0);
|
|
693
|
+
return srcset?.trim().split(/\s+/)[0]?.replace(/,+$/, "");
|
|
662
694
|
}
|
|
663
695
|
|
|
664
696
|
function assetTypeFromData(data: string | undefined): string | undefined {
|
|
@@ -760,7 +792,7 @@ function matchSetIndex(node: XmlNode): number {
|
|
|
760
792
|
return siblings.indexOf(node);
|
|
761
793
|
}
|
|
762
794
|
|
|
763
|
-
const objectElementNames = new Set(["object", "img"]);
|
|
764
|
-
const mediaElementNames = new Set(["audio", "video",
|
|
795
|
+
const objectElementNames = new Set(["object", "img", "picture"]);
|
|
796
|
+
const mediaElementNames = new Set(["audio", "video", "object", "img"]);
|
|
765
797
|
const drawingElementNames = new Set(["picture", ...objectElementNames]);
|
|
766
798
|
const blockBoundaryElementNames = new Set(["p", "div"]);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { parseFiniteNumber, parseXmlBoolean } from "./parser-values.js";
|
|
2
|
+
import type { QtiDiagnostic, QtiProcessingExpression, QtiValue } from "./types.js";
|
|
3
|
+
|
|
4
|
+
type EqualExpression = Extract<QtiProcessingExpression, { type: "equal" }>;
|
|
5
|
+
type EqualityTolerance =
|
|
6
|
+
| { mode: "exact" }
|
|
7
|
+
| {
|
|
8
|
+
mode: "absolute" | "relative";
|
|
9
|
+
lower: string;
|
|
10
|
+
upper: string;
|
|
11
|
+
includeLower: boolean;
|
|
12
|
+
includeUpper: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function parseTolerance(
|
|
16
|
+
attributes: Record<string, string> = {},
|
|
17
|
+
): { ok: true; value: EqualityTolerance } | { ok: false; message: string } {
|
|
18
|
+
const mode = attributes["tolerance-mode"] ?? "exact";
|
|
19
|
+
if (mode !== "exact" && mode !== "absolute" && mode !== "relative") {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
message: "qti-equal requires exact, absolute, or relative tolerance-mode.",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const lowerAttribute = attributes["include-lower-bound"];
|
|
26
|
+
const upperAttribute = attributes["include-upper-bound"];
|
|
27
|
+
const includeLower = lowerAttribute === undefined ? true : parseXmlBoolean(lowerAttribute);
|
|
28
|
+
const includeUpper = upperAttribute === undefined ? true : parseXmlBoolean(upperAttribute);
|
|
29
|
+
if (includeLower === undefined || includeUpper === undefined) {
|
|
30
|
+
return { ok: false, message: "qti-equal boundary flags must be boolean." };
|
|
31
|
+
}
|
|
32
|
+
if (mode === "exact") return { ok: true, value: { mode } };
|
|
33
|
+
const tolerances = attributes.tolerance?.trim().split(/\s+/) ?? [];
|
|
34
|
+
const [lower, upper = lower] = tolerances;
|
|
35
|
+
if (!lower || !upper || tolerances.length > 2) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
message: "qti-equal requires one or two tolerance values in absolute or relative mode.",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return { ok: true, value: { mode, lower, upper, includeLower, includeUpper } };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Validate tolerance attributes and numeric literal or declared-variable operands. */
|
|
45
|
+
export function validateEqualTolerance(
|
|
46
|
+
expression: EqualExpression,
|
|
47
|
+
variables: ReadonlySet<string>,
|
|
48
|
+
diagnostics: QtiDiagnostic[],
|
|
49
|
+
): void {
|
|
50
|
+
const parsed = parseTolerance(expression.attributes);
|
|
51
|
+
const messages: string[] = [];
|
|
52
|
+
if (!parsed.ok) messages.push(parsed.message);
|
|
53
|
+
else if (parsed.value.mode !== "exact") {
|
|
54
|
+
for (const token of [parsed.value.lower, parsed.value.upper]) {
|
|
55
|
+
const number = parseFiniteNumber(token);
|
|
56
|
+
if (number === undefined ? !variables.has(token) : number < 0) {
|
|
57
|
+
messages.push(
|
|
58
|
+
"qti-equal tolerance must be nonnegative numeric values or declared variable references.",
|
|
59
|
+
);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const message of messages) {
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
code: "processing.equal.tolerance",
|
|
67
|
+
severity: "error",
|
|
68
|
+
message,
|
|
69
|
+
source: expression.source,
|
|
70
|
+
path: expression.source?.path,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Compare single numeric operands using QTI absolute/relative tolerance and boundary rules. */
|
|
76
|
+
export function equalWithTolerance(
|
|
77
|
+
expression: EqualExpression,
|
|
78
|
+
left: QtiValue,
|
|
79
|
+
right: QtiValue,
|
|
80
|
+
resolve: (identifier: string) => QtiValue,
|
|
81
|
+
): boolean | null {
|
|
82
|
+
if (typeof left !== "number" || typeof right !== "number") return null;
|
|
83
|
+
const parsed = parseTolerance(expression.attributes);
|
|
84
|
+
if (!parsed.ok) return null;
|
|
85
|
+
const tolerance = parsed.value;
|
|
86
|
+
if (tolerance.mode === "exact") return left === right;
|
|
87
|
+
const lower = parseFiniteNumber(tolerance.lower) ?? resolve(tolerance.lower);
|
|
88
|
+
const upper = parseFiniteNumber(tolerance.upper) ?? resolve(tolerance.upper);
|
|
89
|
+
if (
|
|
90
|
+
typeof lower !== "number" ||
|
|
91
|
+
typeof upper !== "number" ||
|
|
92
|
+
!Number.isFinite(lower) ||
|
|
93
|
+
!Number.isFinite(upper) ||
|
|
94
|
+
lower < 0 ||
|
|
95
|
+
upper < 0
|
|
96
|
+
)
|
|
97
|
+
return null;
|
|
98
|
+
const minimum = tolerance.mode === "absolute" ? left - lower : left * (1 - lower / 100);
|
|
99
|
+
const maximum = tolerance.mode === "absolute" ? left + upper : left * (1 + upper / 100);
|
|
100
|
+
return (
|
|
101
|
+
(tolerance.includeLower ? right >= minimum : right > minimum) &&
|
|
102
|
+
(tolerance.includeUpper ? right <= maximum : right < maximum)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { QtiProcessingExpression, QtiValue } from "./types.js";
|
|
2
|
+
import { equalWithTolerance } from "./processing-equality.js";
|
|
2
3
|
import { assertNever } from "./assert-never.js";
|
|
3
4
|
import type { EvaluationContext } from "./processing-evaluator.js";
|
|
4
5
|
import {
|
|
@@ -47,22 +48,21 @@ export function evaluateComparisonExpression(
|
|
|
47
48
|
case "equal": {
|
|
48
49
|
const left = context.evaluate(expression.left);
|
|
49
50
|
const right = context.evaluate(expression.right);
|
|
50
|
-
return left
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
left,
|
|
54
|
-
right,
|
|
55
|
-
false,
|
|
56
|
-
expressionBaseType(expression.left, context.document) ??
|
|
57
|
-
expressionBaseType(expression.right, context.document),
|
|
58
|
-
);
|
|
51
|
+
return equalWithTolerance(expression, left, right, (identifier) =>
|
|
52
|
+
context.evaluate({ type: "variable", identifier }),
|
|
53
|
+
);
|
|
59
54
|
}
|
|
60
55
|
case "equalRounded": {
|
|
56
|
+
const figures =
|
|
57
|
+
typeof expression.figures === "number"
|
|
58
|
+
? expression.figures
|
|
59
|
+
: context.evaluate({ type: "variable", identifier: expression.figures });
|
|
61
60
|
const validRounding =
|
|
62
|
-
|
|
61
|
+
typeof figures === "number" &&
|
|
62
|
+
Number.isInteger(figures) &&
|
|
63
63
|
(expression.roundingMode === "decimalPlaces"
|
|
64
|
-
?
|
|
65
|
-
: expression.roundingMode === "significantFigures" &&
|
|
64
|
+
? figures >= 0
|
|
65
|
+
: expression.roundingMode === "significantFigures" && figures > 0);
|
|
66
66
|
if (!validRounding) return null;
|
|
67
67
|
const left = context.evaluate(expression.left);
|
|
68
68
|
const right = context.evaluate(expression.right);
|
|
@@ -70,8 +70,8 @@ export function evaluateComparisonExpression(
|
|
|
70
70
|
const leftNumber = numericValueOrNull(left);
|
|
71
71
|
const rightNumber = numericValueOrNull(right);
|
|
72
72
|
if (leftNumber === null || rightNumber === null) return null;
|
|
73
|
-
const roundedLeft = roundWithMode(leftNumber, expression.roundingMode,
|
|
74
|
-
const roundedRight = roundWithMode(rightNumber, expression.roundingMode,
|
|
73
|
+
const roundedLeft = roundWithMode(leftNumber, expression.roundingMode, figures);
|
|
74
|
+
const roundedRight = roundWithMode(rightNumber, expression.roundingMode, figures);
|
|
75
75
|
return roundedLeft === null || roundedRight === null ? null : roundedLeft === roundedRight;
|
|
76
76
|
}
|
|
77
77
|
case "numericCompare": {
|
|
@@ -123,6 +123,11 @@ export function evaluateComparisonExpression(
|
|
|
123
123
|
const baseType =
|
|
124
124
|
expressionBaseType(expression.collection, context.document) ??
|
|
125
125
|
expressionBaseType(expression.values, context.document);
|
|
126
|
+
if (expressionIsOrdered(expression.collection, context.document)) {
|
|
127
|
+
return collection.some((_, offset) =>
|
|
128
|
+
valuesEqual(collection.slice(offset, offset + values.length), values, true, baseType),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
126
131
|
return containsValues(collection, values, baseType);
|
|
127
132
|
}
|
|
128
133
|
default:
|
|
@@ -9,12 +9,8 @@ import {
|
|
|
9
9
|
roundToSignificantFigures,
|
|
10
10
|
statsOperatorValue,
|
|
11
11
|
} from "./processing-operators.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
numericValue,
|
|
15
|
-
numericValueOrNull,
|
|
16
|
-
valueContainer,
|
|
17
|
-
} from "./processing-values.js";
|
|
12
|
+
import { numericValue, numericValueOrNull, valueContainer } from "./processing-values.js";
|
|
13
|
+
import { isRecordValue } from "./value-guards.js";
|
|
18
14
|
|
|
19
15
|
type NumericExpression = Extract<
|
|
20
16
|
QtiProcessingExpression,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { QtiProcessingExpression, QtiValue } from "./types.js";
|
|
2
2
|
import { assertNever } from "./assert-never.js";
|
|
3
3
|
import type { EvaluationContext } from "./processing-evaluator.js";
|
|
4
|
-
import {
|
|
5
|
-
import { isRecordValue
|
|
6
|
-
import {
|
|
4
|
+
import { stringMatch } from "./processing-values.js";
|
|
5
|
+
import { isRecordValue } from "./value-guards.js";
|
|
6
|
+
import { matchXmlSchemaRegex, patternVariableIdentifier } from "./xml-schema-regex.js";
|
|
7
7
|
|
|
8
8
|
type StringExpression = Extract<
|
|
9
9
|
QtiProcessingExpression,
|
|
@@ -32,23 +32,30 @@ export function evaluateStringExpression(
|
|
|
32
32
|
case "patternMatch": {
|
|
33
33
|
const value = context.evaluate(expression.expression);
|
|
34
34
|
if (value === null) return null;
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expression.pattern
|
|
39
|
-
context.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
).test(qtiValueToString(value));
|
|
49
|
-
} catch {
|
|
35
|
+
const reference = patternVariableIdentifier(expression.pattern);
|
|
36
|
+
const pattern =
|
|
37
|
+
reference === undefined
|
|
38
|
+
? expression.pattern
|
|
39
|
+
: context.evaluate({ type: "variable", identifier: reference });
|
|
40
|
+
if (pattern === null) return null;
|
|
41
|
+
if (typeof value !== "string" || typeof pattern !== "string") {
|
|
42
|
+
context.diagnostics.push({
|
|
43
|
+
code: "processing.pattern.type",
|
|
44
|
+
severity: "error",
|
|
45
|
+
message: "Pattern matching requires a single string pattern and operand.",
|
|
46
|
+
source: expression.source,
|
|
47
|
+
});
|
|
50
48
|
return null;
|
|
51
49
|
}
|
|
50
|
+
const result = matchXmlSchemaRegex(pattern, value);
|
|
51
|
+
if (result.ok) return result.matches;
|
|
52
|
+
context.diagnostics.push({
|
|
53
|
+
code: `processing.pattern.${result.problem.code}`,
|
|
54
|
+
severity: "error",
|
|
55
|
+
message: result.problem.message,
|
|
56
|
+
source: expression.source,
|
|
57
|
+
});
|
|
58
|
+
return null;
|
|
52
59
|
}
|
|
53
60
|
case "fieldValue": {
|
|
54
61
|
const value = context.evaluate(expression.expression);
|
|
@@ -29,7 +29,7 @@ export function evaluateVariableExpression(
|
|
|
29
29
|
): QtiValue {
|
|
30
30
|
switch (expression.type) {
|
|
31
31
|
case "isNull":
|
|
32
|
-
return isNullResponse(context.
|
|
32
|
+
return isNullResponse(context.evaluate(expression.expression));
|
|
33
33
|
case "matchCorrect": {
|
|
34
34
|
const declaration = getResponseDeclaration(context.document, expression.correctIdentifier);
|
|
35
35
|
return declaration
|
|
@@ -63,8 +63,12 @@ export function evaluateVariableExpression(
|
|
|
63
63
|
case "correct":
|
|
64
64
|
return context.correctResponses[expression.identifier] ?? null;
|
|
65
65
|
case "default":
|
|
66
|
-
return
|
|
67
|
-
|
|
66
|
+
return Object.hasOwn(context.defaultValues, expression.identifier)
|
|
67
|
+
? (context.defaultValues[expression.identifier] ?? null)
|
|
68
|
+
: defaultValueForIdentifier(context.document, expression.identifier);
|
|
69
|
+
case "variable": {
|
|
70
|
+
const builtIn = context.builtInVariable(expression.identifier);
|
|
71
|
+
if (builtIn !== undefined) return builtIn;
|
|
68
72
|
return (
|
|
69
73
|
resolveVariableValue(
|
|
70
74
|
context.document,
|
|
@@ -76,6 +80,7 @@ export function evaluateVariableExpression(
|
|
|
76
80
|
context.undeclaredResponseValue(expression.identifier) ??
|
|
77
81
|
null
|
|
78
82
|
);
|
|
83
|
+
}
|
|
79
84
|
default:
|
|
80
85
|
return assertNever(expression);
|
|
81
86
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { QtiDocument, QtiProcessingExpression, QtiValue } from "./types.js";
|
|
1
|
+
import type { QtiDiagnostic, QtiDocument, QtiProcessingExpression, QtiValue } from "./types.js";
|
|
2
2
|
import { assertNever } from "./assert-never.js";
|
|
3
3
|
import type { QtiCustomOperatorRegistry } from "./custom-operators.js";
|
|
4
4
|
import { evaluateBooleanExpression } from "./processing-evaluator-boolean.js";
|
|
@@ -12,7 +12,8 @@ import { evaluateGeometryExpression } from "./processing-evaluator-geometry.js";
|
|
|
12
12
|
import { evaluateNumericExpression } from "./processing-evaluator-numeric.js";
|
|
13
13
|
import { evaluateStringExpression } from "./processing-evaluator-string.js";
|
|
14
14
|
import { evaluateVariableExpression } from "./processing-evaluator-variable.js";
|
|
15
|
-
import {
|
|
15
|
+
import { numericValueOrNull } from "./processing-values.js";
|
|
16
|
+
import { isRecordValue } from "./value-guards.js";
|
|
16
17
|
|
|
17
18
|
export interface EvaluationContext {
|
|
18
19
|
document: QtiDocument;
|
|
@@ -20,6 +21,9 @@ export interface EvaluationContext {
|
|
|
20
21
|
outcomes: Record<string, QtiValue>;
|
|
21
22
|
templateValues: Record<string, QtiValue>;
|
|
22
23
|
correctResponses: Record<string, QtiValue>;
|
|
24
|
+
defaultValues: Record<string, QtiValue>;
|
|
25
|
+
diagnostics: QtiDiagnostic[];
|
|
26
|
+
builtInVariable(identifier: string): QtiValue | undefined;
|
|
23
27
|
allowedUndeclaredResponseIdentifiers: ReadonlySet<string>;
|
|
24
28
|
random: () => number;
|
|
25
29
|
customOperators: QtiCustomOperatorRegistry;
|
|
@@ -30,6 +34,7 @@ export interface EvaluationContext {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export interface EvaluationOptions {
|
|
37
|
+
builtInVariable?: ((identifier: string) => QtiValue | undefined) | undefined;
|
|
33
38
|
allowedUndeclaredResponseIdentifiers?: ReadonlySet<string> | readonly string[] | undefined;
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -53,6 +58,14 @@ export function createEvaluationContext(
|
|
|
53
58
|
outcomes,
|
|
54
59
|
templateValues,
|
|
55
60
|
correctResponses,
|
|
61
|
+
defaultValues: {},
|
|
62
|
+
diagnostics: [],
|
|
63
|
+
builtInVariable:
|
|
64
|
+
options.builtInVariable ??
|
|
65
|
+
((identifier) =>
|
|
66
|
+
identifier === "completionStatus"
|
|
67
|
+
? (outcomes.completionStatus ?? "not_attempted")
|
|
68
|
+
: undefined),
|
|
56
69
|
allowedUndeclaredResponseIdentifiers,
|
|
57
70
|
random,
|
|
58
71
|
customOperators,
|
|
@@ -105,11 +118,11 @@ export function evaluateProcessingExpression(
|
|
|
105
118
|
}
|
|
106
119
|
case "randomFloat":
|
|
107
120
|
return expression.min + context.random() * (expression.max - expression.min);
|
|
108
|
-
case "random":
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
case "random": {
|
|
122
|
+
const values = context.evaluate(expression.expression);
|
|
123
|
+
if (!Array.isArray(values) || values.length === 0) return null;
|
|
124
|
+
return values[Math.floor(context.random() * values.length)] ?? null;
|
|
125
|
+
}
|
|
113
126
|
case "isNull":
|
|
114
127
|
case "matchCorrect":
|
|
115
128
|
case "mapResponse":
|
|
@@ -3,8 +3,6 @@ import { assertNever } from "./assert-never.js";
|
|
|
3
3
|
|
|
4
4
|
export function expressionChildren(expression: QtiProcessingExpression): QtiProcessingExpression[] {
|
|
5
5
|
switch (expression.type) {
|
|
6
|
-
case "random":
|
|
7
|
-
return expression.values;
|
|
8
6
|
case "multiple":
|
|
9
7
|
case "ordered":
|
|
10
8
|
case "sum":
|
|
@@ -33,6 +31,8 @@ export function expressionChildren(expression: QtiProcessingExpression): QtiProc
|
|
|
33
31
|
case "stringMatch":
|
|
34
32
|
case "substring":
|
|
35
33
|
return [expression.left, expression.right];
|
|
34
|
+
case "isNull":
|
|
35
|
+
case "random":
|
|
36
36
|
case "not":
|
|
37
37
|
case "round":
|
|
38
38
|
case "roundTo":
|
|
@@ -54,7 +54,6 @@ export function expressionChildren(expression: QtiProcessingExpression): QtiProc
|
|
|
54
54
|
case "baseValue":
|
|
55
55
|
case "correct":
|
|
56
56
|
case "default":
|
|
57
|
-
case "isNull":
|
|
58
57
|
case "mapResponse":
|
|
59
58
|
case "mapResponsePoint":
|
|
60
59
|
case "matchCorrect":
|
|
@@ -17,6 +17,8 @@ export const identifierExpressionTags = {
|
|
|
17
17
|
export type IdentifierExpressionType = keyof typeof identifierExpressionTags;
|
|
18
18
|
|
|
19
19
|
export const unaryExpressionTags = {
|
|
20
|
+
isNull: "qti-is-null",
|
|
21
|
+
random: "qti-random",
|
|
20
22
|
containerSize: "qti-container-size",
|
|
21
23
|
round: "qti-round",
|
|
22
24
|
truncate: "qti-truncate",
|
|
@@ -47,7 +49,6 @@ export const leftRightExpressionTags = {
|
|
|
47
49
|
power: "qti-power",
|
|
48
50
|
integerDivide: "qti-integer-divide",
|
|
49
51
|
integerModulus: "qti-integer-modulus",
|
|
50
|
-
equal: "qti-equal",
|
|
51
52
|
} as const;
|
|
52
53
|
|
|
53
54
|
export type LeftRightExpressionType = keyof typeof leftRightExpressionTags;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { QtiDocument, QtiResponseDeclaration, QtiValue } from "./types.js";
|
|
2
|
-
import { parseQtiPair } from "./parser-values.js";
|
|
2
|
+
import { parseQtiPair, parseXmlBoolean } from "./parser-values.js";
|
|
3
3
|
import { qtiScalarToString, qtiValueToStringList } from "./value-format.js";
|
|
4
|
-
import {
|
|
4
|
+
import { numericValue, valuesEqual } from "./processing-values.js";
|
|
5
|
+
import { isRecordValue } from "./value-guards.js";
|
|
5
6
|
|
|
6
7
|
export function lookupOutcomeValue(
|
|
7
8
|
document: QtiDocument,
|
|
@@ -21,13 +22,11 @@ export function lookupOutcomeValue(
|
|
|
21
22
|
lookupTable.defaultValue
|
|
22
23
|
);
|
|
23
24
|
}
|
|
24
|
-
const entry =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
(candidate)
|
|
28
|
-
|
|
29
|
-
(candidate.includeBoundary !== false && numeric === candidate.sourceValue),
|
|
30
|
-
);
|
|
25
|
+
const entry = lookupTable.entries.find(
|
|
26
|
+
(candidate) =>
|
|
27
|
+
numeric > candidate.sourceValue ||
|
|
28
|
+
(candidate.includeBoundary !== false && numeric === candidate.sourceValue),
|
|
29
|
+
);
|
|
31
30
|
return entry?.targetValue ?? lookupTable.defaultValue;
|
|
32
31
|
}
|
|
33
32
|
|
|
@@ -69,9 +68,12 @@ export function scoreAreaMapping(
|
|
|
69
68
|
for (const [index, entry] of areaMapping.entries.entries()) {
|
|
70
69
|
if (!pointInsideArea(parsed, entry)) continue;
|
|
71
70
|
matchedArea = true;
|
|
72
|
-
if (matchedAreaIndexes.has(index))
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
if (!matchedAreaIndexes.has(index)) {
|
|
72
|
+
matchedAreaIndexes.add(index);
|
|
73
|
+
score += entry.mappedValue;
|
|
74
|
+
}
|
|
75
|
+
// Authored priority applies even when this area was already counted.
|
|
76
|
+
break;
|
|
75
77
|
}
|
|
76
78
|
if (!matchedArea) score += areaMapping.defaultValue;
|
|
77
79
|
}
|
|
@@ -135,23 +137,27 @@ function scoreMapping(
|
|
|
135
137
|
mapping: NonNullable<QtiResponseDeclaration["mapping"]>,
|
|
136
138
|
baseType: QtiResponseDeclaration["baseType"],
|
|
137
139
|
): number {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
const mappedValue = (value: string): number => {
|
|
141
|
+
const key = mappingKey(value, baseType);
|
|
142
|
+
const entry = mapping.entries.find((candidate) => {
|
|
143
|
+
if (candidate.mapKey === undefined) return false;
|
|
144
|
+
const candidateKey = mappingKey(candidate.mapKey, baseType);
|
|
145
|
+
const caseSensitive = parseXmlBoolean(candidate.attributes["case-sensitive"]) ?? false;
|
|
146
|
+
return baseType === "string" && !caseSensitive
|
|
147
|
+
? candidateKey.toLowerCase() === key.toLowerCase()
|
|
148
|
+
: candidateKey === key;
|
|
149
|
+
});
|
|
150
|
+
return entry?.mappedValue ?? mapping.defaultValue;
|
|
151
|
+
};
|
|
143
152
|
if (Array.isArray(response)) {
|
|
144
|
-
const
|
|
145
|
-
(
|
|
146
|
-
|
|
147
|
-
0,
|
|
153
|
+
const distinct = response.filter(
|
|
154
|
+
(value, index) =>
|
|
155
|
+
response.findIndex((candidate) => valuesEqual(candidate, value, false, baseType)) === index,
|
|
148
156
|
);
|
|
157
|
+
const score = distinct.reduce<number>((sum, value) => sum + mappedValue(String(value)), 0);
|
|
149
158
|
return clampMappedScore(score, mapping.attributes);
|
|
150
159
|
}
|
|
151
|
-
const score =
|
|
152
|
-
response === null || isRecordValue(response)
|
|
153
|
-
? 0
|
|
154
|
-
: (values.get(mappingKey(String(response), baseType)) ?? mapping.defaultValue);
|
|
160
|
+
const score = response === null || isRecordValue(response) ? 0 : mappedValue(String(response));
|
|
155
161
|
return clampMappedScore(score, mapping.attributes);
|
|
156
162
|
}
|
|
157
163
|
|