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