@playcademy/sandbox 0.10.1-beta.3 → 0.10.1-beta.5
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 +424 -151
- package/dist/constants.js +1 -1
- package/dist/server.js +424 -151
- 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.5",
|
|
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}$/;
|
|
@@ -47958,7 +47989,7 @@ function rejectSystemManagedReview(input, context2) {
|
|
|
47958
47989
|
});
|
|
47959
47990
|
}
|
|
47960
47991
|
}
|
|
47961
|
-
var TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, CreateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, VerifyTimebackMetricDiscrepancyRequestSchema, AssessmentPurposeSchema, AssessmentStatusSchema, AssessmentKeySchema, DiagnosticKeySchema, DiagnosticRoutingManifestSchema, DiagnosticAssessmentDefinitionInputSchema, AssessmentStandardRefSchema3, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, AssessmentAssociationImportEntrySchema, AttachExistingAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
47992
|
+
var TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, CreateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, VerifyTimebackMetricDiscrepancyRequestSchema, AssessmentPurposeSchema, AssessmentStatusSchema, AssessmentKeySchema, DiagnosticKeySchema, DiagnosticRoutingManifestSchema, DiagnosticAssessmentDefinitionInputSchema, AssessmentStandardRefSchema3, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, ReviseAssessmentRequestSchema, AssessmentAssociationImportEntrySchema, AttachExistingAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
47962
47993
|
var init_schemas4 = __esm(() => {
|
|
47963
47994
|
init_esm();
|
|
47964
47995
|
init_src();
|
|
@@ -48241,6 +48272,11 @@ var init_schemas4 = __esm(() => {
|
|
|
48241
48272
|
requireMasteryStandard(input, context2);
|
|
48242
48273
|
rejectSystemManagedReview(input, context2);
|
|
48243
48274
|
});
|
|
48275
|
+
ReviseAssessmentRequestSchema = exports_external.object({
|
|
48276
|
+
currentQtiTestIdentifier: exports_external.string().trim().min(1, "The current assessment identifier is required"),
|
|
48277
|
+
title: exports_external.string().trim().min(1, "Assessment title is required"),
|
|
48278
|
+
questions: exports_external.array(exports_external.record(exports_external.string(), exports_external.unknown())).min(1, "A revision must contain at least one question").max(500, "A revision cannot contain more than 500 questions")
|
|
48279
|
+
});
|
|
48244
48280
|
AssessmentAssociationImportEntrySchema = exports_external.object({
|
|
48245
48281
|
assessmentKey: AssessmentKeySchema,
|
|
48246
48282
|
qtiTestIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
@@ -94303,6 +94339,30 @@ var init_timeback_game_metrics_comparison_util = __esm(() => {
|
|
|
94303
94339
|
});
|
|
94304
94340
|
|
|
94305
94341
|
// ../timeback/dist/engines/qti/index.js
|
|
94342
|
+
function qtiAttributeNumber(attributes, name3, fallback) {
|
|
94343
|
+
const raw = attributes[name3]?.trim();
|
|
94344
|
+
if (!raw) {
|
|
94345
|
+
return fallback;
|
|
94346
|
+
}
|
|
94347
|
+
const value = Number(raw);
|
|
94348
|
+
return Number.isFinite(value) ? value : fallback;
|
|
94349
|
+
}
|
|
94350
|
+
function qtiAttributeCapacity(attributes, name3, fallback) {
|
|
94351
|
+
const raw = attributes[name3]?.trim();
|
|
94352
|
+
if (!raw) {
|
|
94353
|
+
return fallback;
|
|
94354
|
+
}
|
|
94355
|
+
const value = Number(raw);
|
|
94356
|
+
return Number.isInteger(value) && value >= 0 ? value : null;
|
|
94357
|
+
}
|
|
94358
|
+
function qtiAttributeMaximum(attributes, name3, fallback, unlimited) {
|
|
94359
|
+
const maximum = qtiAttributeNumber(attributes, name3, fallback);
|
|
94360
|
+
return maximum === 0 ? unlimited : maximum;
|
|
94361
|
+
}
|
|
94362
|
+
function qtiAttributeBoolean(attributes, name3) {
|
|
94363
|
+
const value = attributes[name3];
|
|
94364
|
+
return value === "true" || value === "1";
|
|
94365
|
+
}
|
|
94306
94366
|
function decodeCssEscapes(value) {
|
|
94307
94367
|
return value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_, hex3) => {
|
|
94308
94368
|
const code = Number.parseInt(hex3, 16);
|
|
@@ -94890,7 +94950,8 @@ function parseGapTexts(interaction) {
|
|
|
94890
94950
|
return {
|
|
94891
94951
|
identifier: attributes2.identifier || "",
|
|
94892
94952
|
content: plainTextFromNodes(gapText.children),
|
|
94893
|
-
matchMax: finiteNumber(attributes2["match-max"]) ?? 1
|
|
94953
|
+
matchMax: finiteNumber(attributes2["match-max"]) ?? 1,
|
|
94954
|
+
...qtiAttributeBoolean(attributes2, "fixed") ? { fixed: true } : {}
|
|
94894
94955
|
};
|
|
94895
94956
|
}).filter((gapText) => gapText.identifier);
|
|
94896
94957
|
}
|
|
@@ -95990,30 +96051,6 @@ ${body2}
|
|
|
95990
96051
|
${processing}` : ""}
|
|
95991
96052
|
</qti-assessment-item>`;
|
|
95992
96053
|
}
|
|
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
96054
|
function gapContentIdentifiers(nodes) {
|
|
96018
96055
|
return nodes.flatMap((node) => {
|
|
96019
96056
|
if (node.kind === "gap") {
|
|
@@ -96094,6 +96131,7 @@ function authorableQtiInteractionProjection(interaction) {
|
|
|
96094
96131
|
type: interaction.type,
|
|
96095
96132
|
choices: interaction.choices,
|
|
96096
96133
|
minChoices: qtiAttributeNumber(attributes2, "min-choices", 1),
|
|
96134
|
+
shuffle: qtiAttributeBoolean(attributes2, "shuffle"),
|
|
96097
96135
|
maxChoices: qtiAttributeMaximum(attributes2, "max-choices", 1, interaction.choices.length)
|
|
96098
96136
|
};
|
|
96099
96137
|
}
|
|
@@ -96102,6 +96140,7 @@ function authorableQtiInteractionProjection(interaction) {
|
|
|
96102
96140
|
...base,
|
|
96103
96141
|
type: interaction.type,
|
|
96104
96142
|
choices: interaction.choices,
|
|
96143
|
+
shuffle: qtiAttributeBoolean(attributes2, "shuffle"),
|
|
96105
96144
|
blankIndex: interaction.blankIndex ?? 0
|
|
96106
96145
|
} : null;
|
|
96107
96146
|
}
|
|
@@ -99933,11 +99972,13 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99933
99972
|
playableContentNodesSchema = exports_external.array(exports_external.unknown());
|
|
99934
99973
|
playableChoiceSchema = exports_external.object({
|
|
99935
99974
|
identifier: trimmedNonEmptyString3,
|
|
99975
|
+
fixed: exports_external.boolean().optional(),
|
|
99936
99976
|
content: exports_external.string(),
|
|
99937
99977
|
contentNodes: playableContentNodesSchema.optional()
|
|
99938
99978
|
}).strict();
|
|
99939
99979
|
playableMatchChoiceSchema = exports_external.object({
|
|
99940
99980
|
identifier: trimmedNonEmptyString3,
|
|
99981
|
+
fixed: exports_external.boolean().optional(),
|
|
99941
99982
|
content: exports_external.string(),
|
|
99942
99983
|
contentNodes: playableContentNodesSchema.optional(),
|
|
99943
99984
|
matchMax: nonnegativeInteger
|
|
@@ -99957,6 +99998,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99957
99998
|
}).strict();
|
|
99958
99999
|
playableGapTextSchema = exports_external.object({
|
|
99959
100000
|
identifier: trimmedNonEmptyString3,
|
|
100001
|
+
fixed: exports_external.boolean().optional(),
|
|
99960
100002
|
content: exports_external.string(),
|
|
99961
100003
|
matchMax: nonnegativeInteger
|
|
99962
100004
|
}).strict();
|
|
@@ -99974,6 +100016,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99974
100016
|
exports_external.object({
|
|
99975
100017
|
...playableInteractionBase,
|
|
99976
100018
|
type: exports_external.literal("choice"),
|
|
100019
|
+
shuffle: exports_external.boolean(),
|
|
99977
100020
|
choices: exports_external.array(playableChoiceSchema),
|
|
99978
100021
|
minChoices: nonnegativeInteger,
|
|
99979
100022
|
maxChoices: nonnegativeInteger
|
|
@@ -99981,6 +100024,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99981
100024
|
exports_external.object({
|
|
99982
100025
|
...playableInteractionBase,
|
|
99983
100026
|
type: exports_external.literal("inline-choice"),
|
|
100027
|
+
shuffle: exports_external.boolean(),
|
|
99984
100028
|
choices: exports_external.array(playableChoiceSchema)
|
|
99985
100029
|
}).strict(),
|
|
99986
100030
|
exports_external.object({
|
|
@@ -100024,6 +100068,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
100024
100068
|
exports_external.object({
|
|
100025
100069
|
...playableInteractionBase,
|
|
100026
100070
|
type: exports_external.literal("gap-match"),
|
|
100071
|
+
shuffle: exports_external.boolean(),
|
|
100027
100072
|
content: exports_external.string(),
|
|
100028
100073
|
contentNodes: playableContentNodesSchema,
|
|
100029
100074
|
gapTexts: exports_external.array(playableGapTextSchema),
|
|
@@ -100536,6 +100581,7 @@ function playableChoice(choice) {
|
|
|
100536
100581
|
return {
|
|
100537
100582
|
identifier: choice.identifier,
|
|
100538
100583
|
content: choice.content,
|
|
100584
|
+
...qtiAttributeBoolean(choice.attributes, "fixed") ? { fixed: true } : {},
|
|
100539
100585
|
...choice.contentNodes ? { contentNodes: choice.contentNodes } : {}
|
|
100540
100586
|
};
|
|
100541
100587
|
}
|
|
@@ -100598,6 +100644,7 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
100598
100644
|
responseIdentifier,
|
|
100599
100645
|
...prompt,
|
|
100600
100646
|
choices,
|
|
100647
|
+
shuffle: qtiAttributeBoolean(interaction.attributes, "shuffle"),
|
|
100601
100648
|
minChoices: qtiAttributeNumber(interaction.attributes, "min-choices", 1),
|
|
100602
100649
|
maxChoices: qtiAttributeMaximum(interaction.attributes, "max-choices", 1, choices.length)
|
|
100603
100650
|
};
|
|
@@ -100607,7 +100654,8 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
100607
100654
|
type,
|
|
100608
100655
|
responseIdentifier,
|
|
100609
100656
|
...prompt,
|
|
100610
|
-
choices: requireChoices2(interaction.choices.map(playableChoice))
|
|
100657
|
+
choices: requireChoices2(interaction.choices.map(playableChoice)),
|
|
100658
|
+
shuffle: qtiAttributeBoolean(interaction.attributes, "shuffle")
|
|
100611
100659
|
};
|
|
100612
100660
|
}
|
|
100613
100661
|
if (type === "text-entry") {
|
|
@@ -100668,11 +100716,13 @@ function parseInteraction2(questionIdentifier, interaction) {
|
|
|
100668
100716
|
responseIdentifier,
|
|
100669
100717
|
...prompt,
|
|
100670
100718
|
content: interaction.textWithGaps,
|
|
100719
|
+
shuffle: qtiAttributeBoolean(interaction.attributes, "shuffle"),
|
|
100671
100720
|
contentNodes: interaction.gapContent,
|
|
100672
100721
|
gapTexts: interaction.gapTexts.map((gapText) => ({
|
|
100673
100722
|
identifier: gapText.identifier,
|
|
100674
100723
|
content: gapText.content,
|
|
100675
|
-
matchMax: gapText.matchMax
|
|
100724
|
+
matchMax: gapText.matchMax,
|
|
100725
|
+
...gapText.fixed ? { fixed: true } : {}
|
|
100676
100726
|
})),
|
|
100677
100727
|
gaps: [...interaction.gaps],
|
|
100678
100728
|
maxAssociations: qtiAttributeNumber(interaction.attributes, "max-associations", 0)
|
|
@@ -101491,7 +101541,7 @@ function buildInlineChoiceQuestionXml(input, identifier, title) {
|
|
|
101491
101541
|
if (!envelope) {
|
|
101492
101542
|
return null;
|
|
101493
101543
|
}
|
|
101494
|
-
const { structure, responseIdentifier, prompt } = envelope;
|
|
101544
|
+
const { interaction, structure, responseIdentifier, prompt } = envelope;
|
|
101495
101545
|
const promptParts = prompt.split(INLINE_CHOICE_BLANK);
|
|
101496
101546
|
if (promptParts.length !== 2) {
|
|
101497
101547
|
throw new ValidationError("Inline-choice questions require exactly one blank");
|
|
@@ -101505,7 +101555,7 @@ function buildInlineChoiceQuestionXml(input, identifier, title) {
|
|
|
101505
101555
|
const choicesXml = choices.map((choice) => ` <qti-inline-choice identifier="${escapeXml(choice.identifier)}">${escapeXml(choice.content)}</qti-inline-choice>`).join(`
|
|
101506
101556
|
`);
|
|
101507
101557
|
const [before = "", after = ""] = promptParts;
|
|
101508
|
-
const itemBody = ` <p>${escapeXml(before)}<qti-inline-choice-interaction response-identifier="${escapeXml(responseIdentifier)}">
|
|
101558
|
+
const itemBody = ` <p>${escapeXml(before)}<qti-inline-choice-interaction response-identifier="${escapeXml(responseIdentifier)}" shuffle="${interaction.shuffle === true}">
|
|
101509
101559
|
${choicesXml}
|
|
101510
101560
|
</qti-inline-choice-interaction>${escapeXml(after)}</p>`;
|
|
101511
101561
|
return buildExactMatchQuestionXml({
|
|
@@ -102120,7 +102170,7 @@ async function prepareReviewMappingUpdate(test, questions) {
|
|
|
102120
102170
|
const contentRevision = await assessmentContentRevision(test, questions);
|
|
102121
102171
|
const assessment = {
|
|
102122
102172
|
identifier: test.identifier,
|
|
102123
|
-
contractVersion:
|
|
102173
|
+
contractVersion: PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION,
|
|
102124
102174
|
contentRevision,
|
|
102125
102175
|
title: test.title,
|
|
102126
102176
|
items: questions.questions.map(({ question }) => ({
|
|
@@ -102159,6 +102209,7 @@ async function prepareReviewMappingUpdate(test, questions) {
|
|
|
102159
102209
|
var PLAYCADEMY_REVIEW_BANK_TITLE = "Review Bank", PLAYCADEMY_REVIEW_BANK_SINK_METADATA_KEY = "playcademyReviewBankSink", PLAYCADEMY_REVIEW_BANK_SINK_VERSION = 1, StoredSinkWarningSchema, ReviewBankSinkMarkerSchema;
|
|
102160
102210
|
var init_timeback_review_mapping_util = __esm(() => {
|
|
102161
102211
|
init_esm();
|
|
102212
|
+
init_src();
|
|
102162
102213
|
init_assessment_runtime2();
|
|
102163
102214
|
init_qti();
|
|
102164
102215
|
init_timeback3();
|
|
@@ -106176,6 +106227,69 @@ var init_timeback_assessment_import_util = __esm(() => {
|
|
|
106176
106227
|
init_timeback_qti_authoring_util();
|
|
106177
106228
|
});
|
|
106178
106229
|
|
|
106230
|
+
// ../api-core/src/utils/timeback-assessment-revision.util.ts
|
|
106231
|
+
function assertRevisableAssessment(row, reviewBankIdentifier) {
|
|
106232
|
+
if (row.purpose === "review" || row.qtiTestIdentifier === reviewBankIdentifier) {
|
|
106233
|
+
throw new ValidationError("The review bank is system-managed. Revise its mastery sources instead.");
|
|
106234
|
+
}
|
|
106235
|
+
if (row.purpose === "diagnostic") {
|
|
106236
|
+
throw new ValidationError("Diagnostics are revised by importing a new revision and archiving the current one.");
|
|
106237
|
+
}
|
|
106238
|
+
if (row.status !== "live") {
|
|
106239
|
+
throw new ValidationError(row.status === "draft" ? "Draft assessments are edited directly. Only live assessments can be revised in place." : "Archived assessments cannot be revised. Restore it to live first.");
|
|
106240
|
+
}
|
|
106241
|
+
if (!row.assessmentKey) {
|
|
106242
|
+
throw new ValidationError("This unmanaged assessment cannot be revised. Republish it with an explicit assessment key.");
|
|
106243
|
+
}
|
|
106244
|
+
}
|
|
106245
|
+
function assertRevisableTestStructure(current) {
|
|
106246
|
+
const parts2 = qtiTestParts(current);
|
|
106247
|
+
const sectionCount = parts2.reduce((count, part) => count + part["qti-assessment-section"].length, 0);
|
|
106248
|
+
if (parts2.length !== 1 || sectionCount !== 1) {
|
|
106249
|
+
throw new ValidationError(`Assessment ${current.identifier} has ${parts2.length} test part(s) and ${sectionCount} section(s). Revisions can only replace content in single-section assessments.`);
|
|
106250
|
+
}
|
|
106251
|
+
}
|
|
106252
|
+
function buildRevisionSourceTestInput(input) {
|
|
106253
|
+
assertRevisableTestStructure(input.current);
|
|
106254
|
+
const { "qti-outcome-declaration": outcomeDeclarations, ...structure } = buildQtiTestStructureInput(input.current, input.sourceIdentifier);
|
|
106255
|
+
return {
|
|
106256
|
+
identifier: input.sourceIdentifier,
|
|
106257
|
+
title: input.title,
|
|
106258
|
+
...qtiTestOptionalAttributes(input.current),
|
|
106259
|
+
metadata: {
|
|
106260
|
+
...sourceMetadata(input.current.metadata),
|
|
106261
|
+
ownerSystem: PLAYCADEMY_QTI_OWNER_SYSTEM,
|
|
106262
|
+
ownerGameSlug: input.gameSlug,
|
|
106263
|
+
assessmentKey: input.assessmentKey,
|
|
106264
|
+
integrationId: input.integrationId,
|
|
106265
|
+
subject: input.subject,
|
|
106266
|
+
grade: String(input.grade),
|
|
106267
|
+
[PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY]: emptyReviewBankManifest(input.sourceIdentifier)
|
|
106268
|
+
},
|
|
106269
|
+
...outcomeDeclarations && outcomeDeclarations.length > 0 ? { "qti-outcome-declaration": outcomeDeclarations } : {},
|
|
106270
|
+
"qti-test-part": structure["qti-test-part"].map((part) => ({
|
|
106271
|
+
...part,
|
|
106272
|
+
"qti-assessment-section": part["qti-assessment-section"].map((section) => ({
|
|
106273
|
+
...section,
|
|
106274
|
+
"qti-assessment-item-ref": []
|
|
106275
|
+
}))
|
|
106276
|
+
}))
|
|
106277
|
+
};
|
|
106278
|
+
}
|
|
106279
|
+
function revisionConflict(requestedIdentifier, currentIdentifier) {
|
|
106280
|
+
return new ConflictError("This assessment was revised by someone else. Reload to see the current version before revising again.", {
|
|
106281
|
+
requestedQtiTestIdentifier: requestedIdentifier,
|
|
106282
|
+
currentQtiTestIdentifier: currentIdentifier
|
|
106283
|
+
});
|
|
106284
|
+
}
|
|
106285
|
+
var init_timeback_assessment_revision_util = __esm(() => {
|
|
106286
|
+
init_assessment_runtime2();
|
|
106287
|
+
init_timeback3();
|
|
106288
|
+
init_errors2();
|
|
106289
|
+
init_timeback_assessment_publication_util();
|
|
106290
|
+
init_timeback_qti_authoring_util();
|
|
106291
|
+
});
|
|
106292
|
+
|
|
106179
106293
|
// ../api-core/src/utils/timeback-qti-library.util.ts
|
|
106180
106294
|
function newPlaycademyTestIdentifier() {
|
|
106181
106295
|
return `${PLAYCADEMY_QTI_SOURCE_PREFIX}${crypto.randomUUID()}`;
|
|
@@ -106601,6 +106715,109 @@ class TimebackAssessmentsService {
|
|
|
106601
106715
|
throw error88;
|
|
106602
106716
|
}
|
|
106603
106717
|
}
|
|
106718
|
+
async reviseAssessment(integrationId, input) {
|
|
106719
|
+
const client2 = this.requireClient();
|
|
106720
|
+
const reviewBankIdentifier = reviewBankSinkIdentifier(integrationId);
|
|
106721
|
+
const current = await this.requireCurrentRevisionRow(integrationId, input.currentQtiTestIdentifier);
|
|
106722
|
+
assertRevisableAssessment(current, reviewBankIdentifier);
|
|
106723
|
+
const ownership = await this.requireQtiTestOwnershipContext(integrationId);
|
|
106724
|
+
const { integration } = ownership;
|
|
106725
|
+
const creations = input.questions.map((question) => {
|
|
106726
|
+
const creation = parseQtiQuestionCreationInput(question);
|
|
106727
|
+
if (creation.kind === "authoring") {
|
|
106728
|
+
assertSupportedQtiQuestionInteraction(creation.input);
|
|
106729
|
+
}
|
|
106730
|
+
return creation;
|
|
106731
|
+
});
|
|
106732
|
+
assertAssessmentHasQuestions(creations);
|
|
106733
|
+
const currentTest = await client2.qtiApi.assessmentTests.get(current.qtiTestIdentifier);
|
|
106734
|
+
const sourceIdentifier = input.sourceTestIdentifier;
|
|
106735
|
+
const sourceInput = buildRevisionSourceTestInput({
|
|
106736
|
+
sourceIdentifier,
|
|
106737
|
+
title: input.title,
|
|
106738
|
+
current: currentTest,
|
|
106739
|
+
assessmentKey: current.assessmentKey,
|
|
106740
|
+
integrationId,
|
|
106741
|
+
gameSlug: ownership.gameSlug,
|
|
106742
|
+
subject: integration.subject,
|
|
106743
|
+
grade: integration.grade
|
|
106744
|
+
});
|
|
106745
|
+
const createdItemIdentifiers = [];
|
|
106746
|
+
let sourceCreateAttempted = false;
|
|
106747
|
+
try {
|
|
106748
|
+
sourceCreateAttempted = true;
|
|
106749
|
+
await client2.qtiApi.assessmentTests.create(sourceInput);
|
|
106750
|
+
const sourceTest = await client2.qtiApi.assessmentTests.get(sourceIdentifier);
|
|
106751
|
+
for (const creation of creations) {
|
|
106752
|
+
const created = await this.createQuestionInOwnedTest({
|
|
106753
|
+
client: client2,
|
|
106754
|
+
integrationId,
|
|
106755
|
+
qtiTestIdentifier: sourceIdentifier,
|
|
106756
|
+
test: sourceTest,
|
|
106757
|
+
gameSlug: ownership.gameSlug,
|
|
106758
|
+
creation
|
|
106759
|
+
});
|
|
106760
|
+
createdItemIdentifiers.push(created.reference.identifier);
|
|
106761
|
+
}
|
|
106762
|
+
const loaded = await loadHydratedQtiTest(client2, sourceIdentifier);
|
|
106763
|
+
await this.validateAssessmentHasQuestions(loaded);
|
|
106764
|
+
const publication = await this.publishManagedAssessment(current, current.purpose, null, loaded);
|
|
106765
|
+
if (!publication.artifactsAvailable) {
|
|
106766
|
+
throw new ValidationError("The revised assessment could not be compiled for the runtime. The current version stays live; try again.");
|
|
106767
|
+
}
|
|
106768
|
+
const result = await this.deps.db.transaction(async (tx) => {
|
|
106769
|
+
const rows = await tx.select().from(gameTimebackAssessmentTests).where(and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.assessmentKey, current.assessmentKey))).for("update");
|
|
106770
|
+
const row = rows[0];
|
|
106771
|
+
if (!row) {
|
|
106772
|
+
throw new NotFoundError(`Assessment not found: ${input.currentQtiTestIdentifier}`);
|
|
106773
|
+
}
|
|
106774
|
+
if (row.qtiTestIdentifier !== input.currentQtiTestIdentifier) {
|
|
106775
|
+
throw revisionConflict(input.currentQtiTestIdentifier, row.qtiTestIdentifier);
|
|
106776
|
+
}
|
|
106777
|
+
assertRevisableAssessment(row, reviewBankIdentifier);
|
|
106778
|
+
const previousQtiTestIdentifier = row.qtiTestIdentifier;
|
|
106779
|
+
const unchanged = publication.qtiTestIdentifier === previousQtiTestIdentifier;
|
|
106780
|
+
if (unchanged) {
|
|
106781
|
+
setAttribute("app.assessment.operation", "revise_unchanged");
|
|
106782
|
+
return { row, previousQtiTestIdentifier, unchanged };
|
|
106783
|
+
}
|
|
106784
|
+
const [updatedRow] = await tx.update(gameTimebackAssessmentTests).set({
|
|
106785
|
+
qtiTestIdentifier: publication.qtiTestIdentifier,
|
|
106786
|
+
updatedAt: new Date
|
|
106787
|
+
}).where(eq(gameTimebackAssessmentTests.id, row.id)).returning();
|
|
106788
|
+
setAttribute("app.assessment.operation", "revise");
|
|
106789
|
+
addEvent("assessment.revised", {
|
|
106790
|
+
"app.assessment.assessment_key": row.assessmentKey,
|
|
106791
|
+
"app.assessment.previous_qti_test_identifier": previousQtiTestIdentifier,
|
|
106792
|
+
"app.assessment.qti_test_identifier": publication.qtiTestIdentifier
|
|
106793
|
+
});
|
|
106794
|
+
return { row: updatedRow ?? row, previousQtiTestIdentifier, unchanged };
|
|
106795
|
+
});
|
|
106796
|
+
let warning;
|
|
106797
|
+
if (!result.unchanged && result.row.purpose === "mastery") {
|
|
106798
|
+
try {
|
|
106799
|
+
const sync = await this.synchronizeReviewBank(integrationId);
|
|
106800
|
+
if (sync.warnings.length > 0) {
|
|
106801
|
+
warning = `Review bank: ${sync.warnings.map((entry) => entry.message).join(" ")}`;
|
|
106802
|
+
}
|
|
106803
|
+
} catch (error88) {
|
|
106804
|
+
addEvent("assessment.revision_review_bank_sync_failed", {
|
|
106805
|
+
"app.assessment.qti_test_identifier": result.row.qtiTestIdentifier,
|
|
106806
|
+
"exception.type": errorType(error88),
|
|
106807
|
+
"app.error.message": errorMessage(error88)
|
|
106808
|
+
});
|
|
106809
|
+
warning = `The review bank could not be synchronized: ${errorMessage(error88)}. Use Sync Review Bank to retry.`;
|
|
106810
|
+
}
|
|
106811
|
+
}
|
|
106812
|
+
return {
|
|
106813
|
+
...this.associationSummary(result.row, warning),
|
|
106814
|
+
previousQtiTestIdentifier: result.previousQtiTestIdentifier,
|
|
106815
|
+
unchanged: result.unchanged
|
|
106816
|
+
};
|
|
106817
|
+
} finally {
|
|
106818
|
+
await this.cleanupQtiAssessmentCopy(client2, sourceCreateAttempted ? sourceIdentifier : undefined, createdItemIdentifiers);
|
|
106819
|
+
}
|
|
106820
|
+
}
|
|
106604
106821
|
async removeAssessment(integrationId, qtiTestIdentifier) {
|
|
106605
106822
|
return this.withLockedAssessmentAssociations(integrationId, qtiTestIdentifier, async (row, tx) => {
|
|
106606
106823
|
if (qtiTestIdentifier === reviewBankSinkIdentifier(integrationId)) {
|
|
@@ -106873,106 +107090,118 @@ class TimebackAssessmentsService {
|
|
|
106873
107090
|
assertAllAssessmentAssociationsDraft(associations);
|
|
106874
107091
|
const ownership = await this.requireOwnedDraftAssessment(integrationId, qtiTestIdentifier, row, tx);
|
|
106875
107092
|
const creation = parseQtiQuestionCreationInput(input);
|
|
106876
|
-
const
|
|
106877
|
-
|
|
106878
|
-
|
|
106879
|
-
|
|
106880
|
-
|
|
106881
|
-
|
|
106882
|
-
|
|
106883
|
-
|
|
106884
|
-
const copy = buildQtiQuestionCopyInput({
|
|
106885
|
-
source,
|
|
106886
|
-
targetIdentifier: itemIdentifier,
|
|
106887
|
-
targetTestIdentifier: qtiTestIdentifier,
|
|
106888
|
-
gameSlug: ownership.gameSlug,
|
|
106889
|
-
integrationId
|
|
106890
|
-
});
|
|
106891
|
-
title = copy.title;
|
|
106892
|
-
metadata2 = copy.metadata;
|
|
106893
|
-
xml = copy.xml;
|
|
106894
|
-
fallbackItem = {
|
|
106895
|
-
...source,
|
|
106896
|
-
identifier: itemIdentifier,
|
|
106897
|
-
title,
|
|
106898
|
-
rawXml: xml,
|
|
106899
|
-
metadata: metadata2
|
|
106900
|
-
};
|
|
106901
|
-
} else {
|
|
106902
|
-
structuredInput = creation.input;
|
|
106903
|
-
assertSupportedQtiQuestionInteraction(structuredInput);
|
|
106904
|
-
title = typeof structuredInput.title === "string" ? structuredInput.title.trim() : "";
|
|
106905
|
-
metadata2 = buildOwnedQtiQuestionMetadata(structuredInput, {
|
|
106906
|
-
gameSlug: ownership.gameSlug,
|
|
106907
|
-
testIdentifier: qtiTestIdentifier
|
|
106908
|
-
});
|
|
106909
|
-
xml = buildQuestionXml(structuredInput, itemIdentifier, title);
|
|
106910
|
-
fallbackItem = {
|
|
106911
|
-
...structuredInput,
|
|
106912
|
-
identifier: itemIdentifier,
|
|
106913
|
-
title,
|
|
106914
|
-
...xml ? { rawXml: xml } : {}
|
|
106915
|
-
};
|
|
106916
|
-
}
|
|
106917
|
-
const section = await this.qtiSectionItems(client2, qtiTestIdentifier, undefined, undefined, ownership.test);
|
|
106918
|
-
const href = qtiItemHref(client2, itemIdentifier);
|
|
106919
|
-
let item;
|
|
106920
|
-
let itemCreationAttempted = false;
|
|
106921
|
-
let referenceCreationAttempted = false;
|
|
106922
|
-
try {
|
|
106923
|
-
itemCreationAttempted = true;
|
|
106924
|
-
item = xml ? await client2.course.createAssessmentItemXml({ xml, metadata: metadata2 }) : await client2.qtiApi.assessmentItems.create({
|
|
106925
|
-
...structuredInput,
|
|
106926
|
-
identifier: itemIdentifier,
|
|
106927
|
-
metadata: metadata2
|
|
106928
|
-
});
|
|
106929
|
-
referenceCreationAttempted = true;
|
|
106930
|
-
await section.items.add({
|
|
106931
|
-
identifier: itemIdentifier,
|
|
106932
|
-
href
|
|
106933
|
-
});
|
|
106934
|
-
} catch (error88) {
|
|
106935
|
-
let referenceRemoved = !referenceCreationAttempted;
|
|
106936
|
-
const creationStatus = isApiError(error88) ? error88.statusCode : undefined;
|
|
106937
|
-
const itemCreationDefinitelyRejected = !referenceCreationAttempted && typeof creationStatus === "number" && creationStatus >= 400 && creationStatus < 500 && creationStatus !== 408;
|
|
106938
|
-
if (referenceCreationAttempted) {
|
|
106939
|
-
try {
|
|
106940
|
-
await section.items.remove(itemIdentifier);
|
|
106941
|
-
referenceRemoved = true;
|
|
106942
|
-
} catch (cleanupError) {
|
|
106943
|
-
referenceRemoved = isApiError(cleanupError) && cleanupError.statusCode === 404;
|
|
106944
|
-
addEvent("assessment.qti_question_reference_cleanup_failed", {
|
|
106945
|
-
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
106946
|
-
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
106947
|
-
"exception.type": errorType(cleanupError),
|
|
106948
|
-
"app.error.message": errorMessage(cleanupError)
|
|
106949
|
-
});
|
|
106950
|
-
}
|
|
106951
|
-
}
|
|
106952
|
-
if (itemCreationAttempted && !itemCreationDefinitelyRejected && referenceRemoved) {
|
|
106953
|
-
try {
|
|
106954
|
-
await client2.qtiApi.assessmentItems.delete(itemIdentifier);
|
|
106955
|
-
} catch (cleanupError) {
|
|
106956
|
-
addEvent("assessment.qti_question_cleanup_failed", {
|
|
106957
|
-
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
106958
|
-
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
106959
|
-
"exception.type": errorType(cleanupError),
|
|
106960
|
-
"app.error.message": errorMessage(cleanupError)
|
|
106961
|
-
});
|
|
106962
|
-
}
|
|
106963
|
-
}
|
|
106964
|
-
throw error88;
|
|
106965
|
-
}
|
|
107093
|
+
const created = await this.createQuestionInOwnedTest({
|
|
107094
|
+
client: client2,
|
|
107095
|
+
integrationId,
|
|
107096
|
+
qtiTestIdentifier,
|
|
107097
|
+
test: ownership.test,
|
|
107098
|
+
gameSlug: ownership.gameSlug,
|
|
107099
|
+
creation
|
|
107100
|
+
});
|
|
106966
107101
|
setAttribute("app.assessment.operation", creation.kind === "copy" ? "copy_question" : "create_question");
|
|
106967
|
-
return
|
|
106968
|
-
|
|
106969
|
-
|
|
106970
|
-
|
|
106971
|
-
|
|
106972
|
-
|
|
106973
|
-
|
|
107102
|
+
return created;
|
|
107103
|
+
});
|
|
107104
|
+
}
|
|
107105
|
+
async createQuestionInOwnedTest(input) {
|
|
107106
|
+
const { client: client2, integrationId, qtiTestIdentifier, creation } = input;
|
|
107107
|
+
const itemIdentifier = newPlaycademyQuestionIdentifier(qtiTestIdentifier);
|
|
107108
|
+
let title;
|
|
107109
|
+
let metadata2;
|
|
107110
|
+
let xml;
|
|
107111
|
+
let structuredInput;
|
|
107112
|
+
let fallbackItem;
|
|
107113
|
+
if (creation.kind === "copy") {
|
|
107114
|
+
const source = await client2.qtiApi.assessmentItems.get(creation.sourceItemIdentifier);
|
|
107115
|
+
const copy = buildQtiQuestionCopyInput({
|
|
107116
|
+
source,
|
|
107117
|
+
targetIdentifier: itemIdentifier,
|
|
107118
|
+
targetTestIdentifier: qtiTestIdentifier,
|
|
107119
|
+
gameSlug: input.gameSlug,
|
|
107120
|
+
integrationId
|
|
107121
|
+
});
|
|
107122
|
+
title = copy.title;
|
|
107123
|
+
metadata2 = copy.metadata;
|
|
107124
|
+
xml = copy.xml;
|
|
107125
|
+
fallbackItem = {
|
|
107126
|
+
...source,
|
|
107127
|
+
identifier: itemIdentifier,
|
|
107128
|
+
title,
|
|
107129
|
+
rawXml: xml,
|
|
106974
107130
|
metadata: metadata2
|
|
107131
|
+
};
|
|
107132
|
+
} else {
|
|
107133
|
+
structuredInput = creation.input;
|
|
107134
|
+
assertSupportedQtiQuestionInteraction(structuredInput);
|
|
107135
|
+
title = typeof structuredInput.title === "string" ? structuredInput.title.trim() : "";
|
|
107136
|
+
metadata2 = buildOwnedQtiQuestionMetadata(structuredInput, {
|
|
107137
|
+
gameSlug: input.gameSlug,
|
|
107138
|
+
testIdentifier: qtiTestIdentifier
|
|
106975
107139
|
});
|
|
107140
|
+
xml = buildQuestionXml(structuredInput, itemIdentifier, title);
|
|
107141
|
+
fallbackItem = {
|
|
107142
|
+
...structuredInput,
|
|
107143
|
+
identifier: itemIdentifier,
|
|
107144
|
+
title,
|
|
107145
|
+
...xml ? { rawXml: xml } : {}
|
|
107146
|
+
};
|
|
107147
|
+
}
|
|
107148
|
+
const section = await this.qtiSectionItems(client2, qtiTestIdentifier, undefined, undefined, input.test);
|
|
107149
|
+
const href = qtiItemHref(client2, itemIdentifier);
|
|
107150
|
+
let item;
|
|
107151
|
+
let itemCreationAttempted = false;
|
|
107152
|
+
let referenceCreationAttempted = false;
|
|
107153
|
+
try {
|
|
107154
|
+
itemCreationAttempted = true;
|
|
107155
|
+
item = xml ? await client2.course.createAssessmentItemXml({ xml, metadata: metadata2 }) : await client2.qtiApi.assessmentItems.create({
|
|
107156
|
+
...structuredInput,
|
|
107157
|
+
identifier: itemIdentifier,
|
|
107158
|
+
metadata: metadata2
|
|
107159
|
+
});
|
|
107160
|
+
referenceCreationAttempted = true;
|
|
107161
|
+
await section.items.add({
|
|
107162
|
+
identifier: itemIdentifier,
|
|
107163
|
+
href
|
|
107164
|
+
});
|
|
107165
|
+
} catch (error88) {
|
|
107166
|
+
let referenceRemoved = !referenceCreationAttempted;
|
|
107167
|
+
const creationStatus = isApiError(error88) ? error88.statusCode : undefined;
|
|
107168
|
+
const itemCreationDefinitelyRejected = !referenceCreationAttempted && typeof creationStatus === "number" && creationStatus >= 400 && creationStatus < 500 && creationStatus !== 408;
|
|
107169
|
+
if (referenceCreationAttempted) {
|
|
107170
|
+
try {
|
|
107171
|
+
await section.items.remove(itemIdentifier);
|
|
107172
|
+
referenceRemoved = true;
|
|
107173
|
+
} catch (cleanupError) {
|
|
107174
|
+
referenceRemoved = isApiError(cleanupError) && cleanupError.statusCode === 404;
|
|
107175
|
+
addEvent("assessment.qti_question_reference_cleanup_failed", {
|
|
107176
|
+
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107177
|
+
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
107178
|
+
"exception.type": errorType(cleanupError),
|
|
107179
|
+
"app.error.message": errorMessage(cleanupError)
|
|
107180
|
+
});
|
|
107181
|
+
}
|
|
107182
|
+
}
|
|
107183
|
+
if (itemCreationAttempted && !itemCreationDefinitelyRejected && referenceRemoved) {
|
|
107184
|
+
try {
|
|
107185
|
+
await client2.qtiApi.assessmentItems.delete(itemIdentifier);
|
|
107186
|
+
} catch (cleanupError) {
|
|
107187
|
+
addEvent("assessment.qti_question_cleanup_failed", {
|
|
107188
|
+
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107189
|
+
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
107190
|
+
"exception.type": errorType(cleanupError),
|
|
107191
|
+
"app.error.message": errorMessage(cleanupError)
|
|
107192
|
+
});
|
|
107193
|
+
}
|
|
107194
|
+
}
|
|
107195
|
+
throw error88;
|
|
107196
|
+
}
|
|
107197
|
+
return buildCreatedQtiQuestionReference({
|
|
107198
|
+
item,
|
|
107199
|
+
fallbackItem,
|
|
107200
|
+
itemIdentifier,
|
|
107201
|
+
href,
|
|
107202
|
+
partIdentifier: section.partIdentifier,
|
|
107203
|
+
sectionIdentifier: section.sectionIdentifier,
|
|
107204
|
+
metadata: metadata2
|
|
106976
107205
|
});
|
|
106977
107206
|
}
|
|
106978
107207
|
async updateQuestion(integrationId, qtiTestIdentifier, itemIdentifier, input) {
|
|
@@ -107220,6 +107449,22 @@ class TimebackAssessmentsService {
|
|
|
107220
107449
|
}
|
|
107221
107450
|
return row;
|
|
107222
107451
|
}
|
|
107452
|
+
async requireCurrentRevisionRow(integrationId, qtiTestIdentifier) {
|
|
107453
|
+
const row = await this.deps.db.query.gameTimebackAssessmentTests.findFirst({
|
|
107454
|
+
where: and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.qtiTestIdentifier, qtiTestIdentifier))
|
|
107455
|
+
});
|
|
107456
|
+
if (row) {
|
|
107457
|
+
return row;
|
|
107458
|
+
}
|
|
107459
|
+
const assessmentKey = assessmentKeyFromManagedQtiIdentifier(qtiTestIdentifier);
|
|
107460
|
+
const successor = assessmentKey ? await this.deps.db.query.gameTimebackAssessmentTests.findFirst({
|
|
107461
|
+
where: and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.assessmentKey, assessmentKey))
|
|
107462
|
+
}) : undefined;
|
|
107463
|
+
if (successor) {
|
|
107464
|
+
throw revisionConflict(qtiTestIdentifier, successor.qtiTestIdentifier);
|
|
107465
|
+
}
|
|
107466
|
+
throw new NotFoundError(`Assessment not found: ${qtiTestIdentifier}`);
|
|
107467
|
+
}
|
|
107223
107468
|
async requireDraftAssessmentRow(integrationId, qtiTestIdentifier, lockedRow) {
|
|
107224
107469
|
const row = lockedRow ?? await this.requireAssessmentRow(integrationId, qtiTestIdentifier);
|
|
107225
107470
|
assertDraftAssessment(row);
|
|
@@ -107367,9 +107612,21 @@ class TimebackAssessmentsService {
|
|
|
107367
107612
|
itemHref: (identifier) => qtiItemHref(client2, identifier)
|
|
107368
107613
|
});
|
|
107369
107614
|
const artifactSource = await this.ensureManagedQtiPublication(client2, plan);
|
|
107370
|
-
const artifactsAvailable = await this.storeManagedArtifacts(artifactSource, managedAssessmentDiagnosticRoutingManifest(artifactSource.test, plan.assessmentKey));
|
|
107615
|
+
const artifactsAvailable = await this.managedArtifactsStored(plan.qtiTestIdentifier) || await this.storeManagedArtifacts(artifactSource, managedAssessmentDiagnosticRoutingManifest(artifactSource.test, plan.assessmentKey));
|
|
107371
107616
|
const warning = assessmentArtifactPublicationWarning(artifactsAvailable);
|
|
107372
|
-
return { ...plan, ...warning ? { warning } : {} };
|
|
107617
|
+
return { ...plan, artifactsAvailable, ...warning ? { warning } : {} };
|
|
107618
|
+
}
|
|
107619
|
+
async managedArtifactsStored(qtiTestIdentifier) {
|
|
107620
|
+
try {
|
|
107621
|
+
return await this.deps.assessmentArtifacts.loadManagedPlayable(qtiTestIdentifier) !== null;
|
|
107622
|
+
} catch (error88) {
|
|
107623
|
+
addEvent("assessment.artifact_lookup_failed", {
|
|
107624
|
+
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107625
|
+
"exception.type": errorType(error88),
|
|
107626
|
+
"app.error.message": errorMessage(error88)
|
|
107627
|
+
});
|
|
107628
|
+
return false;
|
|
107629
|
+
}
|
|
107373
107630
|
}
|
|
107374
107631
|
async cleanupQtiAssessmentCopy(client2, testIdentifier, itemIdentifiers) {
|
|
107375
107632
|
let testDeleted = !testIdentifier;
|
|
@@ -107496,6 +107753,7 @@ var init_timeback_assessments_service = __esm(async () => {
|
|
|
107496
107753
|
init_timeback_assessment_diagnostic_util();
|
|
107497
107754
|
init_timeback_assessment_import_util();
|
|
107498
107755
|
init_timeback_assessment_publication_util();
|
|
107756
|
+
init_timeback_assessment_revision_util();
|
|
107499
107757
|
init_timeback_assessment_rules_util();
|
|
107500
107758
|
init_timeback_qti_authoring_util();
|
|
107501
107759
|
init_timeback_qti_hydration_util();
|
|
@@ -167505,7 +167763,7 @@ function parseQtiLibraryParams(searchParams) {
|
|
|
167505
167763
|
limit
|
|
167506
167764
|
};
|
|
167507
167765
|
}
|
|
167508
|
-
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, resumeRuntimeAssessment, saveRuntimeAssessment, recordRuntimeAssessmentProgress, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
167766
|
+
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, resumeRuntimeAssessment, saveRuntimeAssessment, recordRuntimeAssessmentProgress, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, reviseAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
167509
167767
|
var init_timeback_controller = __esm(() => {
|
|
167510
167768
|
init_esm();
|
|
167511
167769
|
init_src();
|
|
@@ -168250,6 +168508,20 @@ var init_timeback_controller = __esm(() => {
|
|
|
168250
168508
|
const body2 = await parseRequestBody(ctx.request, UpdateAssessmentRequestSchema);
|
|
168251
168509
|
return ctx.services.timebackAssessments.updateAssessment(integrationId, testIdentifier, body2);
|
|
168252
168510
|
});
|
|
168511
|
+
reviseAssessment = requireDeveloper(async (ctx) => {
|
|
168512
|
+
const { gameId, courseId } = ctx.params;
|
|
168513
|
+
if (!gameId || !courseId) {
|
|
168514
|
+
throw ApiError.badRequest("Missing gameId or courseId parameter");
|
|
168515
|
+
}
|
|
168516
|
+
const body2 = await parseRequestBody(ctx.request, ReviseAssessmentRequestSchema);
|
|
168517
|
+
const integrationId = await ctx.services.timebackAssessments.resolveIntegrationId(gameId, courseId, ctx.user);
|
|
168518
|
+
return ctx.services.timebackAssessments.reviseAssessment(integrationId, {
|
|
168519
|
+
currentQtiTestIdentifier: body2.currentQtiTestIdentifier,
|
|
168520
|
+
sourceTestIdentifier: newPlaycademyTestIdentifier(),
|
|
168521
|
+
title: body2.title,
|
|
168522
|
+
questions: body2.questions
|
|
168523
|
+
});
|
|
168524
|
+
});
|
|
168253
168525
|
synchronizeReviewBank = requireDeveloper(async (ctx) => {
|
|
168254
168526
|
const { gameId, courseId } = ctx.params;
|
|
168255
168527
|
if (!gameId || !courseId) {
|
|
@@ -168400,6 +168672,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
168400
168672
|
createAssessment,
|
|
168401
168673
|
attachExistingAssessments,
|
|
168402
168674
|
updateAssessment,
|
|
168675
|
+
reviseAssessment,
|
|
168403
168676
|
synchronizeReviewBank,
|
|
168404
168677
|
removeAssessment,
|
|
168405
168678
|
reorderQuestions,
|