@playcademy/sandbox 0.10.1-beta.3 → 0.10.1-beta.4
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/dist/cli.js +96 -50
- package/dist/constants.js +1 -1
- package/dist/server.js +96 -50
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -285,7 +285,7 @@ var init_platform = __esm(() => {
|
|
|
285
285
|
var PLATFORM_TIMEZONE = "America/New_York";
|
|
286
286
|
|
|
287
287
|
// ../constants/src/timeback.ts
|
|
288
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION =
|
|
288
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION = 2, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_KEY = "playcademyAssessment", PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY = "playcademyAssessmentItem", TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS, TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY", TIMEBACK_ORG_NAME = "Playcademy Studios", TIMEBACK_ORG_TYPE = "department", TIMEBACK_COURSE_DEFAULTS, TIMEBACK_RESOURCE_DEFAULTS, TIMEBACK_COMPONENT_DEFAULTS, TIMEBACK_COMPONENT_RESOURCE_DEFAULTS, TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH = 2048, TIMEBACK_GAME_METRIC_DECIMAL_PLACES, TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE;
|
|
289
289
|
var init_timeback2 = __esm(() => {
|
|
290
290
|
TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
|
291
291
|
TIMEBACK_SUBJECTS = [
|
|
@@ -1128,7 +1128,7 @@ var package_default;
|
|
|
1128
1128
|
var init_package = __esm(() => {
|
|
1129
1129
|
package_default = {
|
|
1130
1130
|
name: "@playcademy/sandbox",
|
|
1131
|
-
version: "0.10.1-beta.
|
|
1131
|
+
version: "0.10.1-beta.4",
|
|
1132
1132
|
description: "Local development server for Playcademy game development",
|
|
1133
1133
|
type: "module",
|
|
1134
1134
|
exports: {
|
|
@@ -10834,30 +10834,60 @@ function comparePresentationText(left, right) {
|
|
|
10834
10834
|
}
|
|
10835
10835
|
return 0;
|
|
10836
10836
|
}
|
|
10837
|
+
function presentationRandomIndex(seed) {
|
|
10838
|
+
let state = presentationHash(seed);
|
|
10839
|
+
const range = 2 ** 32;
|
|
10840
|
+
return (bound) => {
|
|
10841
|
+
const limit = range - range % bound;
|
|
10842
|
+
let value;
|
|
10843
|
+
do {
|
|
10844
|
+
state = state + 1831565813 >>> 0;
|
|
10845
|
+
let mixed = Math.imul(state ^ state >>> 15, state | 1);
|
|
10846
|
+
mixed ^= mixed + Math.imul(mixed ^ mixed >>> 7, mixed | 61);
|
|
10847
|
+
value = (mixed ^ mixed >>> 14) >>> 0;
|
|
10848
|
+
} while (value >= limit);
|
|
10849
|
+
return value % bound;
|
|
10850
|
+
};
|
|
10851
|
+
}
|
|
10837
10852
|
function presentationChoices(choices, attemptId, itemIdentifier, domain) {
|
|
10838
|
-
const
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10853
|
+
const randomIndex = presentationRandomIndex(JSON.stringify([attemptId, itemIdentifier, domain]));
|
|
10854
|
+
const movable = choices.filter((choice) => !choice.fixed).toSorted((left, right) => comparePresentationText(left.identifier, right.identifier));
|
|
10855
|
+
for (let index = movable.length - 1;index > 0; index--) {
|
|
10856
|
+
const other = randomIndex(index + 1);
|
|
10857
|
+
[movable[index], movable[other]] = [movable[other], movable[index]];
|
|
10858
|
+
}
|
|
10859
|
+
let next = 0;
|
|
10860
|
+
return choices.map((choice) => choice.fixed ? choice : movable[next++]);
|
|
10843
10861
|
}
|
|
10844
10862
|
function assessmentPresentationForAttempt(assessment, attemptId) {
|
|
10845
10863
|
const items = assessment.items.map((item) => {
|
|
10846
10864
|
const interactions = item.interactions.map((interaction) => {
|
|
10847
|
-
if (
|
|
10848
|
-
return
|
|
10849
|
-
...interaction,
|
|
10850
|
-
choices: presentationChoices(interaction.choices, attemptId, item.identifier, `order\x00${interaction.responseIdentifier}`)
|
|
10851
|
-
};
|
|
10865
|
+
if (!("shuffle" in interaction) || !interaction.shuffle) {
|
|
10866
|
+
return interaction;
|
|
10852
10867
|
}
|
|
10853
|
-
|
|
10854
|
-
return {
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10868
|
+
function shuffled(choices, domain) {
|
|
10869
|
+
return presentationChoices(choices, attemptId, item.identifier, `${domain}\x00${interaction.responseIdentifier}`);
|
|
10870
|
+
}
|
|
10871
|
+
switch (interaction.type) {
|
|
10872
|
+
case "choice":
|
|
10873
|
+
case "inline-choice":
|
|
10874
|
+
case "order": {
|
|
10875
|
+
return {
|
|
10876
|
+
...interaction,
|
|
10877
|
+
choices: shuffled(interaction.choices, interaction.type)
|
|
10878
|
+
};
|
|
10879
|
+
}
|
|
10880
|
+
case "gap-match": {
|
|
10881
|
+
return { ...interaction, gapTexts: shuffled(interaction.gapTexts, "gap-match") };
|
|
10882
|
+
}
|
|
10883
|
+
case "match": {
|
|
10884
|
+
return {
|
|
10885
|
+
...interaction,
|
|
10886
|
+
sourceChoices: shuffled(interaction.sourceChoices, "match-source"),
|
|
10887
|
+
targetChoices: shuffled(interaction.targetChoices, "match-target")
|
|
10888
|
+
};
|
|
10889
|
+
}
|
|
10859
10890
|
}
|
|
10860
|
-
return interaction;
|
|
10861
10891
|
});
|
|
10862
10892
|
return { ...item, interactions };
|
|
10863
10893
|
});
|
|
@@ -11812,6 +11842,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11812
11842
|
init_src();
|
|
11813
11843
|
init_timeback3();
|
|
11814
11844
|
init_src();
|
|
11845
|
+
init_src();
|
|
11815
11846
|
init_uuid();
|
|
11816
11847
|
init_esm();
|
|
11817
11848
|
init_src();
|
|
@@ -39209,7 +39240,7 @@ function assertMatchingAssessmentArtifactPair(playable, scoring, expectedIdentit
|
|
|
39209
39240
|
throw new ValidationError("Assessment artifact pair does not describe one revision");
|
|
39210
39241
|
}
|
|
39211
39242
|
}
|
|
39212
|
-
var ASSESSMENT_ARTIFACT_SCHEMA_VERSION =
|
|
39243
|
+
var ASSESSMENT_ARTIFACT_SCHEMA_VERSION = 2, ASSESSMENT_GRADER_PROTOCOL_VERSION = 1, ARTIFACT_KEY_PREFIX = "assessment-artifacts", SHA256_HEX, textEncoder;
|
|
39213
39244
|
var init_assessment_artifact_envelope_util = __esm(() => {
|
|
39214
39245
|
init_errors2();
|
|
39215
39246
|
SHA256_HEX = /^[a-f0-9]{64}$/;
|
|
@@ -94303,6 +94334,30 @@ var init_timeback_game_metrics_comparison_util = __esm(() => {
|
|
|
94303
94334
|
});
|
|
94304
94335
|
|
|
94305
94336
|
// ../timeback/dist/engines/qti/index.js
|
|
94337
|
+
function qtiAttributeNumber(attributes, name3, fallback) {
|
|
94338
|
+
const raw = attributes[name3]?.trim();
|
|
94339
|
+
if (!raw) {
|
|
94340
|
+
return fallback;
|
|
94341
|
+
}
|
|
94342
|
+
const value = Number(raw);
|
|
94343
|
+
return Number.isFinite(value) ? value : fallback;
|
|
94344
|
+
}
|
|
94345
|
+
function qtiAttributeCapacity(attributes, name3, fallback) {
|
|
94346
|
+
const raw = attributes[name3]?.trim();
|
|
94347
|
+
if (!raw) {
|
|
94348
|
+
return fallback;
|
|
94349
|
+
}
|
|
94350
|
+
const value = Number(raw);
|
|
94351
|
+
return Number.isInteger(value) && value >= 0 ? value : null;
|
|
94352
|
+
}
|
|
94353
|
+
function qtiAttributeMaximum(attributes, name3, fallback, unlimited) {
|
|
94354
|
+
const maximum = qtiAttributeNumber(attributes, name3, fallback);
|
|
94355
|
+
return maximum === 0 ? unlimited : maximum;
|
|
94356
|
+
}
|
|
94357
|
+
function qtiAttributeBoolean(attributes, name3) {
|
|
94358
|
+
const value = attributes[name3];
|
|
94359
|
+
return value === "true" || value === "1";
|
|
94360
|
+
}
|
|
94306
94361
|
function decodeCssEscapes(value) {
|
|
94307
94362
|
return value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_, hex3) => {
|
|
94308
94363
|
const code = Number.parseInt(hex3, 16);
|
|
@@ -94890,7 +94945,8 @@ function parseGapTexts(interaction) {
|
|
|
94890
94945
|
return {
|
|
94891
94946
|
identifier: attributes2.identifier || "",
|
|
94892
94947
|
content: plainTextFromNodes(gapText.children),
|
|
94893
|
-
matchMax: finiteNumber(attributes2["match-max"]) ?? 1
|
|
94948
|
+
matchMax: finiteNumber(attributes2["match-max"]) ?? 1,
|
|
94949
|
+
...qtiAttributeBoolean(attributes2, "fixed") ? { fixed: true } : {}
|
|
94894
94950
|
};
|
|
94895
94951
|
}).filter((gapText) => gapText.identifier);
|
|
94896
94952
|
}
|
|
@@ -95990,30 +96046,6 @@ ${body2}
|
|
|
95990
96046
|
${processing}` : ""}
|
|
95991
96047
|
</qti-assessment-item>`;
|
|
95992
96048
|
}
|
|
95993
|
-
function qtiAttributeNumber(attributes2, name3, fallback) {
|
|
95994
|
-
const raw = attributes2[name3]?.trim();
|
|
95995
|
-
if (!raw) {
|
|
95996
|
-
return fallback;
|
|
95997
|
-
}
|
|
95998
|
-
const value = Number(raw);
|
|
95999
|
-
return Number.isFinite(value) ? value : fallback;
|
|
96000
|
-
}
|
|
96001
|
-
function qtiAttributeCapacity(attributes2, name3, fallback) {
|
|
96002
|
-
const raw = attributes2[name3]?.trim();
|
|
96003
|
-
if (!raw) {
|
|
96004
|
-
return fallback;
|
|
96005
|
-
}
|
|
96006
|
-
const value = Number(raw);
|
|
96007
|
-
return Number.isInteger(value) && value >= 0 ? value : null;
|
|
96008
|
-
}
|
|
96009
|
-
function qtiAttributeMaximum(attributes2, name3, fallback, unlimited) {
|
|
96010
|
-
const maximum = qtiAttributeNumber(attributes2, name3, fallback);
|
|
96011
|
-
return maximum === 0 ? unlimited : maximum;
|
|
96012
|
-
}
|
|
96013
|
-
function qtiAttributeBoolean(attributes2, name3) {
|
|
96014
|
-
const value = attributes2[name3];
|
|
96015
|
-
return value === "true" || value === "1";
|
|
96016
|
-
}
|
|
96017
96049
|
function gapContentIdentifiers(nodes) {
|
|
96018
96050
|
return nodes.flatMap((node) => {
|
|
96019
96051
|
if (node.kind === "gap") {
|
|
@@ -96094,6 +96126,7 @@ function authorableQtiInteractionProjection(interaction) {
|
|
|
96094
96126
|
type: interaction.type,
|
|
96095
96127
|
choices: interaction.choices,
|
|
96096
96128
|
minChoices: qtiAttributeNumber(attributes2, "min-choices", 1),
|
|
96129
|
+
shuffle: qtiAttributeBoolean(attributes2, "shuffle"),
|
|
96097
96130
|
maxChoices: qtiAttributeMaximum(attributes2, "max-choices", 1, interaction.choices.length)
|
|
96098
96131
|
};
|
|
96099
96132
|
}
|
|
@@ -96102,6 +96135,7 @@ function authorableQtiInteractionProjection(interaction) {
|
|
|
96102
96135
|
...base,
|
|
96103
96136
|
type: interaction.type,
|
|
96104
96137
|
choices: interaction.choices,
|
|
96138
|
+
shuffle: qtiAttributeBoolean(attributes2, "shuffle"),
|
|
96105
96139
|
blankIndex: interaction.blankIndex ?? 0
|
|
96106
96140
|
} : null;
|
|
96107
96141
|
}
|
|
@@ -99933,11 +99967,13 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99933
99967
|
playableContentNodesSchema = exports_external.array(exports_external.unknown());
|
|
99934
99968
|
playableChoiceSchema = exports_external.object({
|
|
99935
99969
|
identifier: trimmedNonEmptyString3,
|
|
99970
|
+
fixed: exports_external.boolean().optional(),
|
|
99936
99971
|
content: exports_external.string(),
|
|
99937
99972
|
contentNodes: playableContentNodesSchema.optional()
|
|
99938
99973
|
}).strict();
|
|
99939
99974
|
playableMatchChoiceSchema = exports_external.object({
|
|
99940
99975
|
identifier: trimmedNonEmptyString3,
|
|
99976
|
+
fixed: exports_external.boolean().optional(),
|
|
99941
99977
|
content: exports_external.string(),
|
|
99942
99978
|
contentNodes: playableContentNodesSchema.optional(),
|
|
99943
99979
|
matchMax: nonnegativeInteger
|
|
@@ -99957,6 +99993,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99957
99993
|
}).strict();
|
|
99958
99994
|
playableGapTextSchema = exports_external.object({
|
|
99959
99995
|
identifier: trimmedNonEmptyString3,
|
|
99996
|
+
fixed: exports_external.boolean().optional(),
|
|
99960
99997
|
content: exports_external.string(),
|
|
99961
99998
|
matchMax: nonnegativeInteger
|
|
99962
99999
|
}).strict();
|
|
@@ -99974,6 +100011,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99974
100011
|
exports_external.object({
|
|
99975
100012
|
...playableInteractionBase,
|
|
99976
100013
|
type: exports_external.literal("choice"),
|
|
100014
|
+
shuffle: exports_external.boolean(),
|
|
99977
100015
|
choices: exports_external.array(playableChoiceSchema),
|
|
99978
100016
|
minChoices: nonnegativeInteger,
|
|
99979
100017
|
maxChoices: nonnegativeInteger
|
|
@@ -99981,6 +100019,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99981
100019
|
exports_external.object({
|
|
99982
100020
|
...playableInteractionBase,
|
|
99983
100021
|
type: exports_external.literal("inline-choice"),
|
|
100022
|
+
shuffle: exports_external.boolean(),
|
|
99984
100023
|
choices: exports_external.array(playableChoiceSchema)
|
|
99985
100024
|
}).strict(),
|
|
99986
100025
|
exports_external.object({
|
|
@@ -100024,6 +100063,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
100024
100063
|
exports_external.object({
|
|
100025
100064
|
...playableInteractionBase,
|
|
100026
100065
|
type: exports_external.literal("gap-match"),
|
|
100066
|
+
shuffle: exports_external.boolean(),
|
|
100027
100067
|
content: exports_external.string(),
|
|
100028
100068
|
contentNodes: playableContentNodesSchema,
|
|
100029
100069
|
gapTexts: exports_external.array(playableGapTextSchema),
|
|
@@ -100536,6 +100576,7 @@ function playableChoice(choice) {
|
|
|
100536
100576
|
return {
|
|
100537
100577
|
identifier: choice.identifier,
|
|
100538
100578
|
content: choice.content,
|
|
100579
|
+
...qtiAttributeBoolean(choice.attributes, "fixed") ? { fixed: true } : {},
|
|
100539
100580
|
...choice.contentNodes ? { contentNodes: choice.contentNodes } : {}
|
|
100540
100581
|
};
|
|
100541
100582
|
}
|
|
@@ -100598,6 +100639,7 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
100598
100639
|
responseIdentifier,
|
|
100599
100640
|
...prompt,
|
|
100600
100641
|
choices,
|
|
100642
|
+
shuffle: qtiAttributeBoolean(interaction.attributes, "shuffle"),
|
|
100601
100643
|
minChoices: qtiAttributeNumber(interaction.attributes, "min-choices", 1),
|
|
100602
100644
|
maxChoices: qtiAttributeMaximum(interaction.attributes, "max-choices", 1, choices.length)
|
|
100603
100645
|
};
|
|
@@ -100607,7 +100649,8 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
100607
100649
|
type,
|
|
100608
100650
|
responseIdentifier,
|
|
100609
100651
|
...prompt,
|
|
100610
|
-
choices: requireChoices2(interaction.choices.map(playableChoice))
|
|
100652
|
+
choices: requireChoices2(interaction.choices.map(playableChoice)),
|
|
100653
|
+
shuffle: qtiAttributeBoolean(interaction.attributes, "shuffle")
|
|
100611
100654
|
};
|
|
100612
100655
|
}
|
|
100613
100656
|
if (type === "text-entry") {
|
|
@@ -100668,11 +100711,13 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
100668
100711
|
responseIdentifier,
|
|
100669
100712
|
...prompt,
|
|
100670
100713
|
content: interaction.textWithGaps,
|
|
100714
|
+
shuffle: qtiAttributeBoolean(interaction.attributes, "shuffle"),
|
|
100671
100715
|
contentNodes: interaction.gapContent,
|
|
100672
100716
|
gapTexts: interaction.gapTexts.map((gapText) => ({
|
|
100673
100717
|
identifier: gapText.identifier,
|
|
100674
100718
|
content: gapText.content,
|
|
100675
|
-
matchMax: gapText.matchMax
|
|
100719
|
+
matchMax: gapText.matchMax,
|
|
100720
|
+
...gapText.fixed ? { fixed: true } : {}
|
|
100676
100721
|
})),
|
|
100677
100722
|
gaps: [...interaction.gaps],
|
|
100678
100723
|
maxAssociations: qtiAttributeNumber(interaction.attributes, "max-associations", 0)
|
|
@@ -101491,7 +101536,7 @@ function buildInlineChoiceQuestionXml(input, identifier, title) {
|
|
|
101491
101536
|
if (!envelope) {
|
|
101492
101537
|
return null;
|
|
101493
101538
|
}
|
|
101494
|
-
const { structure, responseIdentifier, prompt } = envelope;
|
|
101539
|
+
const { interaction, structure, responseIdentifier, prompt } = envelope;
|
|
101495
101540
|
const promptParts = prompt.split(INLINE_CHOICE_BLANK);
|
|
101496
101541
|
if (promptParts.length !== 2) {
|
|
101497
101542
|
throw new ValidationError("Inline-choice questions require exactly one blank");
|
|
@@ -101505,7 +101550,7 @@ function buildInlineChoiceQuestionXml(input, identifier, title) {
|
|
|
101505
101550
|
const choicesXml = choices.map((choice) => ` <qti-inline-choice identifier="${escapeXml(choice.identifier)}">${escapeXml(choice.content)}</qti-inline-choice>`).join(`
|
|
101506
101551
|
`);
|
|
101507
101552
|
const [before = "", after = ""] = promptParts;
|
|
101508
|
-
const itemBody = ` <p>${escapeXml(before)}<qti-inline-choice-interaction response-identifier="${escapeXml(responseIdentifier)}">
|
|
101553
|
+
const itemBody = ` <p>${escapeXml(before)}<qti-inline-choice-interaction response-identifier="${escapeXml(responseIdentifier)}" shuffle="${interaction.shuffle === true}">
|
|
101509
101554
|
${choicesXml}
|
|
101510
101555
|
</qti-inline-choice-interaction>${escapeXml(after)}</p>`;
|
|
101511
101556
|
return buildExactMatchQuestionXml({
|
|
@@ -102120,7 +102165,7 @@ async function prepareReviewMappingUpdate(test, questions) {
|
|
|
102120
102165
|
const contentRevision = await assessmentContentRevision(test, questions);
|
|
102121
102166
|
const assessment = {
|
|
102122
102167
|
identifier: test.identifier,
|
|
102123
|
-
contractVersion:
|
|
102168
|
+
contractVersion: PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION,
|
|
102124
102169
|
contentRevision,
|
|
102125
102170
|
title: test.title,
|
|
102126
102171
|
items: questions.questions.map(({ question }) => ({
|
|
@@ -102159,6 +102204,7 @@ async function prepareReviewMappingUpdate(test, questions) {
|
|
|
102159
102204
|
var PLAYCADEMY_REVIEW_BANK_TITLE = "Review Bank", PLAYCADEMY_REVIEW_BANK_SINK_METADATA_KEY = "playcademyReviewBankSink", PLAYCADEMY_REVIEW_BANK_SINK_VERSION = 1, StoredSinkWarningSchema, ReviewBankSinkMarkerSchema;
|
|
102160
102205
|
var init_timeback_review_mapping_util = __esm(() => {
|
|
102161
102206
|
init_esm();
|
|
102207
|
+
init_src();
|
|
102162
102208
|
init_assessment_runtime2();
|
|
102163
102209
|
init_qti();
|
|
102164
102210
|
init_timeback3();
|
package/dist/constants.js
CHANGED
|
@@ -100,7 +100,7 @@ var init_platform = __esm(() => {
|
|
|
100
100
|
var PLATFORM_TIMEZONE = "America/New_York";
|
|
101
101
|
|
|
102
102
|
// ../constants/src/timeback.ts
|
|
103
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION =
|
|
103
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION = 2, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_KEY = "playcademyAssessment", PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY = "playcademyAssessmentItem", TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS, TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY", TIMEBACK_ORG_NAME = "Playcademy Studios", TIMEBACK_ORG_TYPE = "department", TIMEBACK_COURSE_DEFAULTS, TIMEBACK_RESOURCE_DEFAULTS, TIMEBACK_COMPONENT_DEFAULTS, TIMEBACK_COMPONENT_RESOURCE_DEFAULTS, TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH = 2048, TIMEBACK_GAME_METRIC_DECIMAL_PLACES, TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE;
|
|
104
104
|
var init_timeback = __esm(() => {
|
|
105
105
|
TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
|
106
106
|
TIMEBACK_SUBJECTS = [
|
package/dist/server.js
CHANGED
|
@@ -284,7 +284,7 @@ var init_platform = __esm(() => {
|
|
|
284
284
|
var PLATFORM_TIMEZONE = "America/New_York";
|
|
285
285
|
|
|
286
286
|
// ../constants/src/timeback.ts
|
|
287
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION =
|
|
287
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS, ASSESSMENT_PURPOSES, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_VERSION = 1, PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION = 2, PLAYCADEMY_ASSESSMENT_RESULT_METADATA_KEY = "playcademyAssessment", PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY = "playcademyAssessmentItem", TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS, TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY", TIMEBACK_ORG_NAME = "Playcademy Studios", TIMEBACK_ORG_TYPE = "department", TIMEBACK_COURSE_DEFAULTS, TIMEBACK_RESOURCE_DEFAULTS, TIMEBACK_COMPONENT_DEFAULTS, TIMEBACK_COMPONENT_RESOURCE_DEFAULTS, TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH = 2048, TIMEBACK_GAME_METRIC_DECIMAL_PLACES, TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE;
|
|
288
288
|
var init_timeback2 = __esm(() => {
|
|
289
289
|
TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
|
290
290
|
TIMEBACK_SUBJECTS = [
|
|
@@ -1127,7 +1127,7 @@ var package_default;
|
|
|
1127
1127
|
var init_package = __esm(() => {
|
|
1128
1128
|
package_default = {
|
|
1129
1129
|
name: "@playcademy/sandbox",
|
|
1130
|
-
version: "0.10.1-beta.
|
|
1130
|
+
version: "0.10.1-beta.4",
|
|
1131
1131
|
description: "Local development server for Playcademy game development",
|
|
1132
1132
|
type: "module",
|
|
1133
1133
|
exports: {
|
|
@@ -10833,30 +10833,60 @@ function comparePresentationText(left, right) {
|
|
|
10833
10833
|
}
|
|
10834
10834
|
return 0;
|
|
10835
10835
|
}
|
|
10836
|
+
function presentationRandomIndex(seed) {
|
|
10837
|
+
let state = presentationHash(seed);
|
|
10838
|
+
const range = 2 ** 32;
|
|
10839
|
+
return (bound) => {
|
|
10840
|
+
const limit = range - range % bound;
|
|
10841
|
+
let value;
|
|
10842
|
+
do {
|
|
10843
|
+
state = state + 1831565813 >>> 0;
|
|
10844
|
+
let mixed = Math.imul(state ^ state >>> 15, state | 1);
|
|
10845
|
+
mixed ^= mixed + Math.imul(mixed ^ mixed >>> 7, mixed | 61);
|
|
10846
|
+
value = (mixed ^ mixed >>> 14) >>> 0;
|
|
10847
|
+
} while (value >= limit);
|
|
10848
|
+
return value % bound;
|
|
10849
|
+
};
|
|
10850
|
+
}
|
|
10836
10851
|
function presentationChoices(choices, attemptId, itemIdentifier, domain) {
|
|
10837
|
-
const
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10852
|
+
const randomIndex = presentationRandomIndex(JSON.stringify([attemptId, itemIdentifier, domain]));
|
|
10853
|
+
const movable = choices.filter((choice) => !choice.fixed).toSorted((left, right) => comparePresentationText(left.identifier, right.identifier));
|
|
10854
|
+
for (let index = movable.length - 1;index > 0; index--) {
|
|
10855
|
+
const other = randomIndex(index + 1);
|
|
10856
|
+
[movable[index], movable[other]] = [movable[other], movable[index]];
|
|
10857
|
+
}
|
|
10858
|
+
let next = 0;
|
|
10859
|
+
return choices.map((choice) => choice.fixed ? choice : movable[next++]);
|
|
10842
10860
|
}
|
|
10843
10861
|
function assessmentPresentationForAttempt(assessment, attemptId) {
|
|
10844
10862
|
const items = assessment.items.map((item) => {
|
|
10845
10863
|
const interactions = item.interactions.map((interaction) => {
|
|
10846
|
-
if (
|
|
10847
|
-
return
|
|
10848
|
-
...interaction,
|
|
10849
|
-
choices: presentationChoices(interaction.choices, attemptId, item.identifier, `order\x00${interaction.responseIdentifier}`)
|
|
10850
|
-
};
|
|
10864
|
+
if (!("shuffle" in interaction) || !interaction.shuffle) {
|
|
10865
|
+
return interaction;
|
|
10851
10866
|
}
|
|
10852
|
-
|
|
10853
|
-
return {
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10867
|
+
function shuffled(choices, domain) {
|
|
10868
|
+
return presentationChoices(choices, attemptId, item.identifier, `${domain}\x00${interaction.responseIdentifier}`);
|
|
10869
|
+
}
|
|
10870
|
+
switch (interaction.type) {
|
|
10871
|
+
case "choice":
|
|
10872
|
+
case "inline-choice":
|
|
10873
|
+
case "order": {
|
|
10874
|
+
return {
|
|
10875
|
+
...interaction,
|
|
10876
|
+
choices: shuffled(interaction.choices, interaction.type)
|
|
10877
|
+
};
|
|
10878
|
+
}
|
|
10879
|
+
case "gap-match": {
|
|
10880
|
+
return { ...interaction, gapTexts: shuffled(interaction.gapTexts, "gap-match") };
|
|
10881
|
+
}
|
|
10882
|
+
case "match": {
|
|
10883
|
+
return {
|
|
10884
|
+
...interaction,
|
|
10885
|
+
sourceChoices: shuffled(interaction.sourceChoices, "match-source"),
|
|
10886
|
+
targetChoices: shuffled(interaction.targetChoices, "match-target")
|
|
10887
|
+
};
|
|
10888
|
+
}
|
|
10858
10889
|
}
|
|
10859
|
-
return interaction;
|
|
10860
10890
|
});
|
|
10861
10891
|
return { ...item, interactions };
|
|
10862
10892
|
});
|
|
@@ -11811,6 +11841,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11811
11841
|
init_src();
|
|
11812
11842
|
init_timeback3();
|
|
11813
11843
|
init_src();
|
|
11844
|
+
init_src();
|
|
11814
11845
|
init_uuid();
|
|
11815
11846
|
init_esm();
|
|
11816
11847
|
init_src();
|
|
@@ -39208,7 +39239,7 @@ function assertMatchingAssessmentArtifactPair(playable, scoring, expectedIdentit
|
|
|
39208
39239
|
throw new ValidationError("Assessment artifact pair does not describe one revision");
|
|
39209
39240
|
}
|
|
39210
39241
|
}
|
|
39211
|
-
var ASSESSMENT_ARTIFACT_SCHEMA_VERSION =
|
|
39242
|
+
var ASSESSMENT_ARTIFACT_SCHEMA_VERSION = 2, ASSESSMENT_GRADER_PROTOCOL_VERSION = 1, ARTIFACT_KEY_PREFIX = "assessment-artifacts", SHA256_HEX, textEncoder;
|
|
39212
39243
|
var init_assessment_artifact_envelope_util = __esm(() => {
|
|
39213
39244
|
init_errors2();
|
|
39214
39245
|
SHA256_HEX = /^[a-f0-9]{64}$/;
|
|
@@ -94302,6 +94333,30 @@ var init_timeback_game_metrics_comparison_util = __esm(() => {
|
|
|
94302
94333
|
});
|
|
94303
94334
|
|
|
94304
94335
|
// ../timeback/dist/engines/qti/index.js
|
|
94336
|
+
function qtiAttributeNumber(attributes, name3, fallback) {
|
|
94337
|
+
const raw = attributes[name3]?.trim();
|
|
94338
|
+
if (!raw) {
|
|
94339
|
+
return fallback;
|
|
94340
|
+
}
|
|
94341
|
+
const value = Number(raw);
|
|
94342
|
+
return Number.isFinite(value) ? value : fallback;
|
|
94343
|
+
}
|
|
94344
|
+
function qtiAttributeCapacity(attributes, name3, fallback) {
|
|
94345
|
+
const raw = attributes[name3]?.trim();
|
|
94346
|
+
if (!raw) {
|
|
94347
|
+
return fallback;
|
|
94348
|
+
}
|
|
94349
|
+
const value = Number(raw);
|
|
94350
|
+
return Number.isInteger(value) && value >= 0 ? value : null;
|
|
94351
|
+
}
|
|
94352
|
+
function qtiAttributeMaximum(attributes, name3, fallback, unlimited) {
|
|
94353
|
+
const maximum = qtiAttributeNumber(attributes, name3, fallback);
|
|
94354
|
+
return maximum === 0 ? unlimited : maximum;
|
|
94355
|
+
}
|
|
94356
|
+
function qtiAttributeBoolean(attributes, name3) {
|
|
94357
|
+
const value = attributes[name3];
|
|
94358
|
+
return value === "true" || value === "1";
|
|
94359
|
+
}
|
|
94305
94360
|
function decodeCssEscapes(value) {
|
|
94306
94361
|
return value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_, hex3) => {
|
|
94307
94362
|
const code = Number.parseInt(hex3, 16);
|
|
@@ -94889,7 +94944,8 @@ function parseGapTexts(interaction) {
|
|
|
94889
94944
|
return {
|
|
94890
94945
|
identifier: attributes2.identifier || "",
|
|
94891
94946
|
content: plainTextFromNodes(gapText.children),
|
|
94892
|
-
matchMax: finiteNumber(attributes2["match-max"]) ?? 1
|
|
94947
|
+
matchMax: finiteNumber(attributes2["match-max"]) ?? 1,
|
|
94948
|
+
...qtiAttributeBoolean(attributes2, "fixed") ? { fixed: true } : {}
|
|
94893
94949
|
};
|
|
94894
94950
|
}).filter((gapText) => gapText.identifier);
|
|
94895
94951
|
}
|
|
@@ -95989,30 +96045,6 @@ ${body2}
|
|
|
95989
96045
|
${processing}` : ""}
|
|
95990
96046
|
</qti-assessment-item>`;
|
|
95991
96047
|
}
|
|
95992
|
-
function qtiAttributeNumber(attributes2, name3, fallback) {
|
|
95993
|
-
const raw = attributes2[name3]?.trim();
|
|
95994
|
-
if (!raw) {
|
|
95995
|
-
return fallback;
|
|
95996
|
-
}
|
|
95997
|
-
const value = Number(raw);
|
|
95998
|
-
return Number.isFinite(value) ? value : fallback;
|
|
95999
|
-
}
|
|
96000
|
-
function qtiAttributeCapacity(attributes2, name3, fallback) {
|
|
96001
|
-
const raw = attributes2[name3]?.trim();
|
|
96002
|
-
if (!raw) {
|
|
96003
|
-
return fallback;
|
|
96004
|
-
}
|
|
96005
|
-
const value = Number(raw);
|
|
96006
|
-
return Number.isInteger(value) && value >= 0 ? value : null;
|
|
96007
|
-
}
|
|
96008
|
-
function qtiAttributeMaximum(attributes2, name3, fallback, unlimited) {
|
|
96009
|
-
const maximum = qtiAttributeNumber(attributes2, name3, fallback);
|
|
96010
|
-
return maximum === 0 ? unlimited : maximum;
|
|
96011
|
-
}
|
|
96012
|
-
function qtiAttributeBoolean(attributes2, name3) {
|
|
96013
|
-
const value = attributes2[name3];
|
|
96014
|
-
return value === "true" || value === "1";
|
|
96015
|
-
}
|
|
96016
96048
|
function gapContentIdentifiers(nodes) {
|
|
96017
96049
|
return nodes.flatMap((node) => {
|
|
96018
96050
|
if (node.kind === "gap") {
|
|
@@ -96093,6 +96125,7 @@ function authorableQtiInteractionProjection(interaction) {
|
|
|
96093
96125
|
type: interaction.type,
|
|
96094
96126
|
choices: interaction.choices,
|
|
96095
96127
|
minChoices: qtiAttributeNumber(attributes2, "min-choices", 1),
|
|
96128
|
+
shuffle: qtiAttributeBoolean(attributes2, "shuffle"),
|
|
96096
96129
|
maxChoices: qtiAttributeMaximum(attributes2, "max-choices", 1, interaction.choices.length)
|
|
96097
96130
|
};
|
|
96098
96131
|
}
|
|
@@ -96101,6 +96134,7 @@ function authorableQtiInteractionProjection(interaction) {
|
|
|
96101
96134
|
...base,
|
|
96102
96135
|
type: interaction.type,
|
|
96103
96136
|
choices: interaction.choices,
|
|
96137
|
+
shuffle: qtiAttributeBoolean(attributes2, "shuffle"),
|
|
96104
96138
|
blankIndex: interaction.blankIndex ?? 0
|
|
96105
96139
|
} : null;
|
|
96106
96140
|
}
|
|
@@ -99932,11 +99966,13 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99932
99966
|
playableContentNodesSchema = exports_external.array(exports_external.unknown());
|
|
99933
99967
|
playableChoiceSchema = exports_external.object({
|
|
99934
99968
|
identifier: trimmedNonEmptyString3,
|
|
99969
|
+
fixed: exports_external.boolean().optional(),
|
|
99935
99970
|
content: exports_external.string(),
|
|
99936
99971
|
contentNodes: playableContentNodesSchema.optional()
|
|
99937
99972
|
}).strict();
|
|
99938
99973
|
playableMatchChoiceSchema = exports_external.object({
|
|
99939
99974
|
identifier: trimmedNonEmptyString3,
|
|
99975
|
+
fixed: exports_external.boolean().optional(),
|
|
99940
99976
|
content: exports_external.string(),
|
|
99941
99977
|
contentNodes: playableContentNodesSchema.optional(),
|
|
99942
99978
|
matchMax: nonnegativeInteger
|
|
@@ -99956,6 +99992,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99956
99992
|
}).strict();
|
|
99957
99993
|
playableGapTextSchema = exports_external.object({
|
|
99958
99994
|
identifier: trimmedNonEmptyString3,
|
|
99995
|
+
fixed: exports_external.boolean().optional(),
|
|
99959
99996
|
content: exports_external.string(),
|
|
99960
99997
|
matchMax: nonnegativeInteger
|
|
99961
99998
|
}).strict();
|
|
@@ -99973,6 +100010,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99973
100010
|
exports_external.object({
|
|
99974
100011
|
...playableInteractionBase,
|
|
99975
100012
|
type: exports_external.literal("choice"),
|
|
100013
|
+
shuffle: exports_external.boolean(),
|
|
99976
100014
|
choices: exports_external.array(playableChoiceSchema),
|
|
99977
100015
|
minChoices: nonnegativeInteger,
|
|
99978
100016
|
maxChoices: nonnegativeInteger
|
|
@@ -99980,6 +100018,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99980
100018
|
exports_external.object({
|
|
99981
100019
|
...playableInteractionBase,
|
|
99982
100020
|
type: exports_external.literal("inline-choice"),
|
|
100021
|
+
shuffle: exports_external.boolean(),
|
|
99983
100022
|
choices: exports_external.array(playableChoiceSchema)
|
|
99984
100023
|
}).strict(),
|
|
99985
100024
|
exports_external.object({
|
|
@@ -100023,6 +100062,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
100023
100062
|
exports_external.object({
|
|
100024
100063
|
...playableInteractionBase,
|
|
100025
100064
|
type: exports_external.literal("gap-match"),
|
|
100065
|
+
shuffle: exports_external.boolean(),
|
|
100026
100066
|
content: exports_external.string(),
|
|
100027
100067
|
contentNodes: playableContentNodesSchema,
|
|
100028
100068
|
gapTexts: exports_external.array(playableGapTextSchema),
|
|
@@ -100535,6 +100575,7 @@ function playableChoice(choice) {
|
|
|
100535
100575
|
return {
|
|
100536
100576
|
identifier: choice.identifier,
|
|
100537
100577
|
content: choice.content,
|
|
100578
|
+
...qtiAttributeBoolean(choice.attributes, "fixed") ? { fixed: true } : {},
|
|
100538
100579
|
...choice.contentNodes ? { contentNodes: choice.contentNodes } : {}
|
|
100539
100580
|
};
|
|
100540
100581
|
}
|
|
@@ -100597,6 +100638,7 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
100597
100638
|
responseIdentifier,
|
|
100598
100639
|
...prompt,
|
|
100599
100640
|
choices,
|
|
100641
|
+
shuffle: qtiAttributeBoolean(interaction.attributes, "shuffle"),
|
|
100600
100642
|
minChoices: qtiAttributeNumber(interaction.attributes, "min-choices", 1),
|
|
100601
100643
|
maxChoices: qtiAttributeMaximum(interaction.attributes, "max-choices", 1, choices.length)
|
|
100602
100644
|
};
|
|
@@ -100606,7 +100648,8 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
100606
100648
|
type,
|
|
100607
100649
|
responseIdentifier,
|
|
100608
100650
|
...prompt,
|
|
100609
|
-
choices: requireChoices2(interaction.choices.map(playableChoice))
|
|
100651
|
+
choices: requireChoices2(interaction.choices.map(playableChoice)),
|
|
100652
|
+
shuffle: qtiAttributeBoolean(interaction.attributes, "shuffle")
|
|
100610
100653
|
};
|
|
100611
100654
|
}
|
|
100612
100655
|
if (type === "text-entry") {
|
|
@@ -100667,11 +100710,13 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
100667
100710
|
responseIdentifier,
|
|
100668
100711
|
...prompt,
|
|
100669
100712
|
content: interaction.textWithGaps,
|
|
100713
|
+
shuffle: qtiAttributeBoolean(interaction.attributes, "shuffle"),
|
|
100670
100714
|
contentNodes: interaction.gapContent,
|
|
100671
100715
|
gapTexts: interaction.gapTexts.map((gapText) => ({
|
|
100672
100716
|
identifier: gapText.identifier,
|
|
100673
100717
|
content: gapText.content,
|
|
100674
|
-
matchMax: gapText.matchMax
|
|
100718
|
+
matchMax: gapText.matchMax,
|
|
100719
|
+
...gapText.fixed ? { fixed: true } : {}
|
|
100675
100720
|
})),
|
|
100676
100721
|
gaps: [...interaction.gaps],
|
|
100677
100722
|
maxAssociations: qtiAttributeNumber(interaction.attributes, "max-associations", 0)
|
|
@@ -101490,7 +101535,7 @@ function buildInlineChoiceQuestionXml(input, identifier, title) {
|
|
|
101490
101535
|
if (!envelope) {
|
|
101491
101536
|
return null;
|
|
101492
101537
|
}
|
|
101493
|
-
const { structure, responseIdentifier, prompt } = envelope;
|
|
101538
|
+
const { interaction, structure, responseIdentifier, prompt } = envelope;
|
|
101494
101539
|
const promptParts = prompt.split(INLINE_CHOICE_BLANK);
|
|
101495
101540
|
if (promptParts.length !== 2) {
|
|
101496
101541
|
throw new ValidationError("Inline-choice questions require exactly one blank");
|
|
@@ -101504,7 +101549,7 @@ function buildInlineChoiceQuestionXml(input, identifier, title) {
|
|
|
101504
101549
|
const choicesXml = choices.map((choice) => ` <qti-inline-choice identifier="${escapeXml(choice.identifier)}">${escapeXml(choice.content)}</qti-inline-choice>`).join(`
|
|
101505
101550
|
`);
|
|
101506
101551
|
const [before = "", after = ""] = promptParts;
|
|
101507
|
-
const itemBody = ` <p>${escapeXml(before)}<qti-inline-choice-interaction response-identifier="${escapeXml(responseIdentifier)}">
|
|
101552
|
+
const itemBody = ` <p>${escapeXml(before)}<qti-inline-choice-interaction response-identifier="${escapeXml(responseIdentifier)}" shuffle="${interaction.shuffle === true}">
|
|
101508
101553
|
${choicesXml}
|
|
101509
101554
|
</qti-inline-choice-interaction>${escapeXml(after)}</p>`;
|
|
101510
101555
|
return buildExactMatchQuestionXml({
|
|
@@ -102119,7 +102164,7 @@ async function prepareReviewMappingUpdate(test, questions) {
|
|
|
102119
102164
|
const contentRevision = await assessmentContentRevision(test, questions);
|
|
102120
102165
|
const assessment = {
|
|
102121
102166
|
identifier: test.identifier,
|
|
102122
|
-
contractVersion:
|
|
102167
|
+
contractVersion: PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION,
|
|
102123
102168
|
contentRevision,
|
|
102124
102169
|
title: test.title,
|
|
102125
102170
|
items: questions.questions.map(({ question }) => ({
|
|
@@ -102158,6 +102203,7 @@ async function prepareReviewMappingUpdate(test, questions) {
|
|
|
102158
102203
|
var PLAYCADEMY_REVIEW_BANK_TITLE = "Review Bank", PLAYCADEMY_REVIEW_BANK_SINK_METADATA_KEY = "playcademyReviewBankSink", PLAYCADEMY_REVIEW_BANK_SINK_VERSION = 1, StoredSinkWarningSchema, ReviewBankSinkMarkerSchema;
|
|
102159
102204
|
var init_timeback_review_mapping_util = __esm(() => {
|
|
102160
102205
|
init_esm();
|
|
102206
|
+
init_src();
|
|
102161
102207
|
init_assessment_runtime2();
|
|
102162
102208
|
init_qti();
|
|
102163
102209
|
init_timeback3();
|