@playcademy/sandbox 0.7.1-beta.20 → 0.7.1-beta.21
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 +272 -3
- package/dist/server.js +272 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1123,7 +1123,7 @@ var package_default;
|
|
|
1123
1123
|
var init_package = __esm(() => {
|
|
1124
1124
|
package_default = {
|
|
1125
1125
|
name: "@playcademy/sandbox",
|
|
1126
|
-
version: "0.7.1-beta.
|
|
1126
|
+
version: "0.7.1-beta.21",
|
|
1127
1127
|
description: "Local development server for Playcademy game development",
|
|
1128
1128
|
type: "module",
|
|
1129
1129
|
exports: {
|
|
@@ -39631,7 +39631,7 @@ function requireMasteryStandard(input, context2) {
|
|
|
39631
39631
|
});
|
|
39632
39632
|
}
|
|
39633
39633
|
}
|
|
39634
|
-
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, AssessmentStandardRefSchema2, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
39634
|
+
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, AssessmentStandardRefSchema2, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, AssessmentAssociationImportEntrySchema, AttachExistingAssessmentsRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
39635
39635
|
var init_schemas4 = __esm(() => {
|
|
39636
39636
|
init_esm();
|
|
39637
39637
|
init_src();
|
|
@@ -39898,6 +39898,43 @@ var init_schemas4 = __esm(() => {
|
|
|
39898
39898
|
purpose: AssessmentPurposeSchema,
|
|
39899
39899
|
standard: AssessmentStandardRefSchema2.optional()
|
|
39900
39900
|
}).superRefine(requireMasteryStandard);
|
|
39901
|
+
AssessmentAssociationImportEntrySchema = exports_external.object({
|
|
39902
|
+
qtiTestIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
39903
|
+
purpose: AssessmentPurposeSchema,
|
|
39904
|
+
standard: AssessmentStandardRefSchema2.optional(),
|
|
39905
|
+
sortOrder: exports_external.number().int().nonnegative().nullable()
|
|
39906
|
+
}).superRefine((input, context2) => {
|
|
39907
|
+
requireMasteryStandard(input, context2);
|
|
39908
|
+
if (input.purpose !== "mastery" && input.standard !== undefined) {
|
|
39909
|
+
context2.addIssue({
|
|
39910
|
+
code: "custom",
|
|
39911
|
+
path: ["standard"],
|
|
39912
|
+
message: "Only mastery assessments may have a quiz-level standard"
|
|
39913
|
+
});
|
|
39914
|
+
}
|
|
39915
|
+
});
|
|
39916
|
+
AttachExistingAssessmentsRequestSchema = exports_external.object({
|
|
39917
|
+
version: exports_external.literal(1),
|
|
39918
|
+
sourceGameId: exports_external.string().trim().min(1),
|
|
39919
|
+
sourceGameSlug: exports_external.string().trim().min(1).optional(),
|
|
39920
|
+
sourceIntegrationId: exports_external.string().trim().min(1),
|
|
39921
|
+
subject: TimebackSubjectSchema,
|
|
39922
|
+
grade: TimebackGradeSchema,
|
|
39923
|
+
targetStatus: exports_external.enum(["draft", "live"]),
|
|
39924
|
+
assessments: exports_external.array(AssessmentAssociationImportEntrySchema).min(1, "At least one assessment is required").max(500, "A bulk import can contain at most 500 assessments")
|
|
39925
|
+
}).superRefine((input, context2) => {
|
|
39926
|
+
const seen = new Set;
|
|
39927
|
+
input.assessments.forEach((assessment, index2) => {
|
|
39928
|
+
if (seen.has(assessment.qtiTestIdentifier)) {
|
|
39929
|
+
context2.addIssue({
|
|
39930
|
+
code: "custom",
|
|
39931
|
+
path: ["assessments", index2, "qtiTestIdentifier"],
|
|
39932
|
+
message: "Assessment identifiers must be unique"
|
|
39933
|
+
});
|
|
39934
|
+
}
|
|
39935
|
+
seen.add(assessment.qtiTestIdentifier);
|
|
39936
|
+
});
|
|
39937
|
+
});
|
|
39901
39938
|
ReorderAssessmentsRequestSchema = exports_external.object({
|
|
39902
39939
|
purpose: AssessmentPurposeSchema,
|
|
39903
39940
|
testIdentifiers: exports_external.array(exports_external.string().trim().min(1, "Assessment identifier is required")).min(1, "At least one assessment is required")
|
|
@@ -94151,6 +94188,76 @@ var init_timeback_qti_authoring_util = __esm(() => {
|
|
|
94151
94188
|
];
|
|
94152
94189
|
});
|
|
94153
94190
|
|
|
94191
|
+
// ../api-core/src/utils/timeback-assessment-import.util.ts
|
|
94192
|
+
function assertPlayableAssessmentImportQuestions(targetStatus, questions) {
|
|
94193
|
+
if (targetStatus === "draft") {
|
|
94194
|
+
return;
|
|
94195
|
+
}
|
|
94196
|
+
for (const question of questions) {
|
|
94197
|
+
assertPlayableQtiQuestion(question);
|
|
94198
|
+
}
|
|
94199
|
+
}
|
|
94200
|
+
function assertAssessmentImportCourseMatches(manifest, integration) {
|
|
94201
|
+
if (integration.subject !== manifest.subject || integration.grade !== manifest.grade) {
|
|
94202
|
+
throw new ValidationError(`This manifest is for ${manifest.subject} grade ${manifest.grade}, not ${integration.subject} grade ${integration.grade}.`);
|
|
94203
|
+
}
|
|
94204
|
+
}
|
|
94205
|
+
function associationMetadataMatches(row, entry) {
|
|
94206
|
+
return row.purpose === entry.purpose && (row.standardFramework ?? undefined) === entry.standard?.framework && (row.standardIdentifier ?? undefined) === entry.standard?.identifier;
|
|
94207
|
+
}
|
|
94208
|
+
function targetStatusSatisfied(row, targetStatus) {
|
|
94209
|
+
return row.status === targetStatus || targetStatus === "draft" && row.status === "live";
|
|
94210
|
+
}
|
|
94211
|
+
function classifyExistingAssessmentImport(row, entry, targetStatus) {
|
|
94212
|
+
if (!associationMetadataMatches(row, entry)) {
|
|
94213
|
+
return {
|
|
94214
|
+
status: "failed",
|
|
94215
|
+
message: "Already attached with different purpose or standard metadata."
|
|
94216
|
+
};
|
|
94217
|
+
}
|
|
94218
|
+
if (targetStatusSatisfied(row, targetStatus)) {
|
|
94219
|
+
return { status: "skipped", message: `Already attached as ${row.status}.` };
|
|
94220
|
+
}
|
|
94221
|
+
if (row.status === "archived") {
|
|
94222
|
+
return {
|
|
94223
|
+
status: "failed",
|
|
94224
|
+
message: "Already attached as archived and cannot be imported again."
|
|
94225
|
+
};
|
|
94226
|
+
}
|
|
94227
|
+
if (targetStatus === "live" && row.status === "draft") {
|
|
94228
|
+
return {
|
|
94229
|
+
status: "failed",
|
|
94230
|
+
message: "Already attached as a draft. Publish it from the assessment editor before retrying."
|
|
94231
|
+
};
|
|
94232
|
+
}
|
|
94233
|
+
return {
|
|
94234
|
+
status: "failed",
|
|
94235
|
+
message: `Already attached as ${row.status}, which does not match the requested ${targetStatus} status.`
|
|
94236
|
+
};
|
|
94237
|
+
}
|
|
94238
|
+
function attachedAssessmentImportMessage(targetStatus, editable) {
|
|
94239
|
+
if (targetStatus === "live") {
|
|
94240
|
+
return editable ? "Attached live." : "Attached live. QTI content is read-only because another app owns it.";
|
|
94241
|
+
}
|
|
94242
|
+
return editable ? "Attached as a draft." : "Attached as a read-only draft owned by another app.";
|
|
94243
|
+
}
|
|
94244
|
+
function liveReviewAssessmentImportFailures(targetStatus, candidates, existingRows) {
|
|
94245
|
+
if (targetStatus === "draft") {
|
|
94246
|
+
return new Map;
|
|
94247
|
+
}
|
|
94248
|
+
const reviews = candidates.filter((candidate) => candidate.purpose === "review");
|
|
94249
|
+
const hasExistingLiveReview = existingRows.some((row) => row.purpose === "review" && row.status === "live");
|
|
94250
|
+
if (reviews.length <= 1 && !hasExistingLiveReview) {
|
|
94251
|
+
return new Map;
|
|
94252
|
+
}
|
|
94253
|
+
const message = hasExistingLiveReview ? "Another review assessment is already live for this course." : "Only one review assessment can be imported live at a time.";
|
|
94254
|
+
return new Map(reviews.map((review) => [review.qtiTestIdentifier, message]));
|
|
94255
|
+
}
|
|
94256
|
+
var init_timeback_assessment_import_util = __esm(() => {
|
|
94257
|
+
init_errors2();
|
|
94258
|
+
init_timeback_qti_authoring_util();
|
|
94259
|
+
});
|
|
94260
|
+
|
|
94154
94261
|
// ../api-core/src/utils/timeback-qti-library.util.ts
|
|
94155
94262
|
function newPlaycademyTestIdentifier() {
|
|
94156
94263
|
return `${PLAYCADEMY_QTI_SOURCE_PREFIX}${crypto.randomUUID()}`;
|
|
@@ -94298,6 +94405,150 @@ class TimebackAssessmentsService {
|
|
|
94298
94405
|
throw error88;
|
|
94299
94406
|
}
|
|
94300
94407
|
}
|
|
94408
|
+
async attachExistingAssessments(integrationId, manifest) {
|
|
94409
|
+
const client2 = this.requireClient();
|
|
94410
|
+
const { integration, gameSlug } = await this.requireQtiTestOwnershipContext(integrationId);
|
|
94411
|
+
assertAssessmentImportCourseMatches(manifest, integration);
|
|
94412
|
+
validateUniqueAssessmentIdentifiers(manifest.assessments.map((assessment) => assessment.qtiTestIdentifier));
|
|
94413
|
+
const entries = manifest.assessments.map((assessment, index2) => ({
|
|
94414
|
+
...assessment,
|
|
94415
|
+
index: index2,
|
|
94416
|
+
standard: this.requirePurposeStandard(assessment.purpose, assessment.standard)
|
|
94417
|
+
}));
|
|
94418
|
+
const existingRows = await this.deps.db.query.gameTimebackAssessmentTests.findMany({
|
|
94419
|
+
where: eq(gameTimebackAssessmentTests.integrationId, integrationId)
|
|
94420
|
+
});
|
|
94421
|
+
const existingByIdentifier = new Map(existingRows.map((row) => [row.qtiTestIdentifier, row]));
|
|
94422
|
+
const validated = await runWithConcurrency(entries, QTI_HYDRATION_CONCURRENCY, async (entry) => {
|
|
94423
|
+
try {
|
|
94424
|
+
const loaded = await loadHydratedQtiTest(client2, entry.qtiTestIdentifier);
|
|
94425
|
+
assertAssessmentHasQuestions(loaded.questions.questions);
|
|
94426
|
+
assertPlayableAssessmentImportQuestions(manifest.targetStatus, loaded.questions.questions.map(({ question }) => question));
|
|
94427
|
+
if (entry.purpose === "review") {
|
|
94428
|
+
assertReviewAssessmentHasStandards(loaded.questions.questions.map(({ question }) => qtiAssessmentStandardRefsFromMetadata2(question.metadata).filter((standard) => canonicalReviewStandardRef(standard) !== null).length));
|
|
94429
|
+
}
|
|
94430
|
+
return { entry, test: loaded.test };
|
|
94431
|
+
} catch (error88) {
|
|
94432
|
+
return { entry, error: error88 };
|
|
94433
|
+
}
|
|
94434
|
+
});
|
|
94435
|
+
const results = [];
|
|
94436
|
+
const pending = [];
|
|
94437
|
+
for (const validation of validated) {
|
|
94438
|
+
const { entry } = validation;
|
|
94439
|
+
const { index: index2 } = entry;
|
|
94440
|
+
const base = {
|
|
94441
|
+
qtiTestIdentifier: entry.qtiTestIdentifier,
|
|
94442
|
+
purpose: entry.purpose,
|
|
94443
|
+
...entry.standard ? { standard: entry.standard } : {},
|
|
94444
|
+
sourceSortOrder: entry.sortOrder
|
|
94445
|
+
};
|
|
94446
|
+
if ("error" in validation) {
|
|
94447
|
+
results[index2] = {
|
|
94448
|
+
...base,
|
|
94449
|
+
status: "failed",
|
|
94450
|
+
message: `QTI validation failed: ${errorMessage(validation.error)}`
|
|
94451
|
+
};
|
|
94452
|
+
} else {
|
|
94453
|
+
const existing = existingByIdentifier.get(entry.qtiTestIdentifier);
|
|
94454
|
+
const editable = isQtiTestOwnedByGame(validation.test, gameSlug);
|
|
94455
|
+
const hydratedBase = { ...base, title: validation.test.title, editable };
|
|
94456
|
+
if (!existing) {
|
|
94457
|
+
pending.push({ entry, test: validation.test });
|
|
94458
|
+
} else {
|
|
94459
|
+
const decision = classifyExistingAssessmentImport(existing, {
|
|
94460
|
+
purpose: entry.purpose,
|
|
94461
|
+
...entry.standard ? { standard: entry.standard } : {}
|
|
94462
|
+
}, manifest.targetStatus);
|
|
94463
|
+
results[index2] = {
|
|
94464
|
+
...hydratedBase,
|
|
94465
|
+
status: decision.status,
|
|
94466
|
+
association: this.associationImportSummary(existing),
|
|
94467
|
+
message: decision.message
|
|
94468
|
+
};
|
|
94469
|
+
}
|
|
94470
|
+
}
|
|
94471
|
+
}
|
|
94472
|
+
const liveReviewFailures = liveReviewAssessmentImportFailures(manifest.targetStatus, pending.map(({ entry }) => entry), existingRows);
|
|
94473
|
+
for (const { entry, test } of pending) {
|
|
94474
|
+
const message = liveReviewFailures.get(entry.qtiTestIdentifier);
|
|
94475
|
+
if (message) {
|
|
94476
|
+
results[entry.index] = {
|
|
94477
|
+
qtiTestIdentifier: entry.qtiTestIdentifier,
|
|
94478
|
+
title: test.title,
|
|
94479
|
+
purpose: entry.purpose,
|
|
94480
|
+
...entry.standard ? { standard: entry.standard } : {},
|
|
94481
|
+
sourceSortOrder: entry.sortOrder,
|
|
94482
|
+
editable: isQtiTestOwnedByGame(test, gameSlug),
|
|
94483
|
+
status: "failed",
|
|
94484
|
+
message
|
|
94485
|
+
};
|
|
94486
|
+
}
|
|
94487
|
+
}
|
|
94488
|
+
const attachablePending = pending.filter(({ entry }) => !liveReviewFailures.has(entry.qtiTestIdentifier));
|
|
94489
|
+
for (const validation of attachablePending) {
|
|
94490
|
+
const { entry, test } = validation;
|
|
94491
|
+
const { index: index2 } = entry;
|
|
94492
|
+
const base = {
|
|
94493
|
+
qtiTestIdentifier: entry.qtiTestIdentifier,
|
|
94494
|
+
title: test.title,
|
|
94495
|
+
purpose: entry.purpose,
|
|
94496
|
+
...entry.standard ? { standard: entry.standard } : {},
|
|
94497
|
+
sourceSortOrder: entry.sortOrder,
|
|
94498
|
+
editable: isQtiTestOwnedByGame(test, gameSlug)
|
|
94499
|
+
};
|
|
94500
|
+
try {
|
|
94501
|
+
const [row] = await this.deps.db.insert(gameTimebackAssessmentTests).values({
|
|
94502
|
+
integrationId,
|
|
94503
|
+
qtiTestIdentifier: entry.qtiTestIdentifier,
|
|
94504
|
+
purpose: entry.purpose,
|
|
94505
|
+
status: manifest.targetStatus,
|
|
94506
|
+
sortOrder: entry.sortOrder,
|
|
94507
|
+
standardFramework: entry.standard?.framework,
|
|
94508
|
+
standardIdentifier: entry.standard?.identifier
|
|
94509
|
+
}).returning();
|
|
94510
|
+
if (!row) {
|
|
94511
|
+
throw new Error("Assessment association create returned no row");
|
|
94512
|
+
}
|
|
94513
|
+
results[index2] = {
|
|
94514
|
+
...base,
|
|
94515
|
+
status: "created",
|
|
94516
|
+
association: this.associationImportSummary(row),
|
|
94517
|
+
message: attachedAssessmentImportMessage(manifest.targetStatus, base.editable)
|
|
94518
|
+
};
|
|
94519
|
+
} catch (error88) {
|
|
94520
|
+
results[index2] = {
|
|
94521
|
+
...base,
|
|
94522
|
+
status: "failed",
|
|
94523
|
+
message: `Association attach failed: ${errorMessage(error88)}`
|
|
94524
|
+
};
|
|
94525
|
+
if (isUniqueViolation(error88)) {
|
|
94526
|
+
let concurrentAssociation;
|
|
94527
|
+
try {
|
|
94528
|
+
concurrentAssociation = await this.deps.db.query.gameTimebackAssessmentTests.findFirst({
|
|
94529
|
+
where: and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.qtiTestIdentifier, entry.qtiTestIdentifier))
|
|
94530
|
+
});
|
|
94531
|
+
} catch {}
|
|
94532
|
+
if (concurrentAssociation) {
|
|
94533
|
+
const decision = classifyExistingAssessmentImport(concurrentAssociation, {
|
|
94534
|
+
purpose: entry.purpose,
|
|
94535
|
+
...entry.standard ? { standard: entry.standard } : {}
|
|
94536
|
+
}, manifest.targetStatus);
|
|
94537
|
+
results[index2] = {
|
|
94538
|
+
...base,
|
|
94539
|
+
status: decision.status,
|
|
94540
|
+
association: this.associationImportSummary(concurrentAssociation),
|
|
94541
|
+
message: decision.message
|
|
94542
|
+
};
|
|
94543
|
+
}
|
|
94544
|
+
}
|
|
94545
|
+
}
|
|
94546
|
+
}
|
|
94547
|
+
setAttribute("app.assessment.operation", "bulk_attach_existing");
|
|
94548
|
+
setAttribute("app.assessment.bulk_attach.target_status", manifest.targetStatus);
|
|
94549
|
+
setAttribute("app.assessment.bulk_attach.created", results.filter((result) => result.status === "created").length);
|
|
94550
|
+
return { results };
|
|
94551
|
+
}
|
|
94301
94552
|
async updateAssessment(integrationId, qtiTestIdentifier, input) {
|
|
94302
94553
|
try {
|
|
94303
94554
|
return await this.withLockedAssessmentAssociations(integrationId, qtiTestIdentifier, async (row, tx, associations) => {
|
|
@@ -94811,6 +95062,13 @@ class TimebackAssessmentsService {
|
|
|
94811
95062
|
updatedAt: row.updatedAt
|
|
94812
95063
|
};
|
|
94813
95064
|
}
|
|
95065
|
+
associationImportSummary(row) {
|
|
95066
|
+
return {
|
|
95067
|
+
...this.associationSummary(row),
|
|
95068
|
+
createdAt: row.createdAt.toISOString(),
|
|
95069
|
+
updatedAt: row.updatedAt.toISOString()
|
|
95070
|
+
};
|
|
95071
|
+
}
|
|
94814
95072
|
canonicalAssessmentStandard(input) {
|
|
94815
95073
|
const standard = canonicalReviewStandardRef(input);
|
|
94816
95074
|
if (!standard) {
|
|
@@ -94857,6 +95115,7 @@ var init_timeback_assessments_service = __esm(async () => {
|
|
|
94857
95115
|
init_qti();
|
|
94858
95116
|
init_timeback3();
|
|
94859
95117
|
init_errors2();
|
|
95118
|
+
init_timeback_assessment_import_util();
|
|
94860
95119
|
init_timeback_assessment_rules_util();
|
|
94861
95120
|
init_timeback_qti_authoring_util();
|
|
94862
95121
|
init_timeback_qti_hydration_util();
|
|
@@ -154837,7 +155096,7 @@ function parseQtiLibraryParams(searchParams) {
|
|
|
154837
155096
|
limit
|
|
154838
155097
|
};
|
|
154839
155098
|
}
|
|
154840
|
-
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, saveRuntimeAssessment, submitRuntimeAssessmentItem, submitRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, updateAssessment, reorderAssessments, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
155099
|
+
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, saveRuntimeAssessment, submitRuntimeAssessmentItem, submitRuntimeAssessment, 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, reorderAssessments, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
154841
155100
|
var init_timeback_controller = __esm(() => {
|
|
154842
155101
|
init_esm();
|
|
154843
155102
|
init_src();
|
|
@@ -155492,6 +155751,15 @@ var init_timeback_controller = __esm(() => {
|
|
|
155492
155751
|
qtiTestIdentifier
|
|
155493
155752
|
});
|
|
155494
155753
|
});
|
|
155754
|
+
attachExistingAssessments = requireDeveloper(async (ctx) => {
|
|
155755
|
+
const { gameId, courseId } = ctx.params;
|
|
155756
|
+
if (!gameId || !courseId) {
|
|
155757
|
+
throw ApiError.badRequest("Missing gameId or courseId parameter");
|
|
155758
|
+
}
|
|
155759
|
+
const manifest = await parseRequestBody(ctx.request, AttachExistingAssessmentsRequestSchema);
|
|
155760
|
+
const integrationId = await ctx.services.timebackAssessments.resolveIntegrationId(gameId, courseId, ctx.user);
|
|
155761
|
+
return ctx.services.timebackAssessments.attachExistingAssessments(integrationId, manifest);
|
|
155762
|
+
});
|
|
155495
155763
|
updateAssessment = requireDeveloper(async (ctx) => {
|
|
155496
155764
|
const { gameId, courseId, testIdentifier } = ctx.params;
|
|
155497
155765
|
if (!gameId || !courseId || !testIdentifier) {
|
|
@@ -155641,6 +155909,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
155641
155909
|
reactivateEnrollment,
|
|
155642
155910
|
listAssessments,
|
|
155643
155911
|
createAssessment,
|
|
155912
|
+
attachExistingAssessments,
|
|
155644
155913
|
updateAssessment,
|
|
155645
155914
|
reorderAssessments,
|
|
155646
155915
|
removeAssessment,
|
package/dist/server.js
CHANGED
|
@@ -1122,7 +1122,7 @@ var package_default;
|
|
|
1122
1122
|
var init_package = __esm(() => {
|
|
1123
1123
|
package_default = {
|
|
1124
1124
|
name: "@playcademy/sandbox",
|
|
1125
|
-
version: "0.7.1-beta.
|
|
1125
|
+
version: "0.7.1-beta.21",
|
|
1126
1126
|
description: "Local development server for Playcademy game development",
|
|
1127
1127
|
type: "module",
|
|
1128
1128
|
exports: {
|
|
@@ -39630,7 +39630,7 @@ function requireMasteryStandard(input, context2) {
|
|
|
39630
39630
|
});
|
|
39631
39631
|
}
|
|
39632
39632
|
}
|
|
39633
|
-
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, AssessmentStandardRefSchema2, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
39633
|
+
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, AssessmentStandardRefSchema2, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, AssessmentAssociationImportEntrySchema, AttachExistingAssessmentsRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
39634
39634
|
var init_schemas4 = __esm(() => {
|
|
39635
39635
|
init_esm();
|
|
39636
39636
|
init_src();
|
|
@@ -39897,6 +39897,43 @@ var init_schemas4 = __esm(() => {
|
|
|
39897
39897
|
purpose: AssessmentPurposeSchema,
|
|
39898
39898
|
standard: AssessmentStandardRefSchema2.optional()
|
|
39899
39899
|
}).superRefine(requireMasteryStandard);
|
|
39900
|
+
AssessmentAssociationImportEntrySchema = exports_external.object({
|
|
39901
|
+
qtiTestIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
39902
|
+
purpose: AssessmentPurposeSchema,
|
|
39903
|
+
standard: AssessmentStandardRefSchema2.optional(),
|
|
39904
|
+
sortOrder: exports_external.number().int().nonnegative().nullable()
|
|
39905
|
+
}).superRefine((input, context2) => {
|
|
39906
|
+
requireMasteryStandard(input, context2);
|
|
39907
|
+
if (input.purpose !== "mastery" && input.standard !== undefined) {
|
|
39908
|
+
context2.addIssue({
|
|
39909
|
+
code: "custom",
|
|
39910
|
+
path: ["standard"],
|
|
39911
|
+
message: "Only mastery assessments may have a quiz-level standard"
|
|
39912
|
+
});
|
|
39913
|
+
}
|
|
39914
|
+
});
|
|
39915
|
+
AttachExistingAssessmentsRequestSchema = exports_external.object({
|
|
39916
|
+
version: exports_external.literal(1),
|
|
39917
|
+
sourceGameId: exports_external.string().trim().min(1),
|
|
39918
|
+
sourceGameSlug: exports_external.string().trim().min(1).optional(),
|
|
39919
|
+
sourceIntegrationId: exports_external.string().trim().min(1),
|
|
39920
|
+
subject: TimebackSubjectSchema,
|
|
39921
|
+
grade: TimebackGradeSchema,
|
|
39922
|
+
targetStatus: exports_external.enum(["draft", "live"]),
|
|
39923
|
+
assessments: exports_external.array(AssessmentAssociationImportEntrySchema).min(1, "At least one assessment is required").max(500, "A bulk import can contain at most 500 assessments")
|
|
39924
|
+
}).superRefine((input, context2) => {
|
|
39925
|
+
const seen = new Set;
|
|
39926
|
+
input.assessments.forEach((assessment, index2) => {
|
|
39927
|
+
if (seen.has(assessment.qtiTestIdentifier)) {
|
|
39928
|
+
context2.addIssue({
|
|
39929
|
+
code: "custom",
|
|
39930
|
+
path: ["assessments", index2, "qtiTestIdentifier"],
|
|
39931
|
+
message: "Assessment identifiers must be unique"
|
|
39932
|
+
});
|
|
39933
|
+
}
|
|
39934
|
+
seen.add(assessment.qtiTestIdentifier);
|
|
39935
|
+
});
|
|
39936
|
+
});
|
|
39900
39937
|
ReorderAssessmentsRequestSchema = exports_external.object({
|
|
39901
39938
|
purpose: AssessmentPurposeSchema,
|
|
39902
39939
|
testIdentifiers: exports_external.array(exports_external.string().trim().min(1, "Assessment identifier is required")).min(1, "At least one assessment is required")
|
|
@@ -94150,6 +94187,76 @@ var init_timeback_qti_authoring_util = __esm(() => {
|
|
|
94150
94187
|
];
|
|
94151
94188
|
});
|
|
94152
94189
|
|
|
94190
|
+
// ../api-core/src/utils/timeback-assessment-import.util.ts
|
|
94191
|
+
function assertPlayableAssessmentImportQuestions(targetStatus, questions) {
|
|
94192
|
+
if (targetStatus === "draft") {
|
|
94193
|
+
return;
|
|
94194
|
+
}
|
|
94195
|
+
for (const question of questions) {
|
|
94196
|
+
assertPlayableQtiQuestion(question);
|
|
94197
|
+
}
|
|
94198
|
+
}
|
|
94199
|
+
function assertAssessmentImportCourseMatches(manifest, integration) {
|
|
94200
|
+
if (integration.subject !== manifest.subject || integration.grade !== manifest.grade) {
|
|
94201
|
+
throw new ValidationError(`This manifest is for ${manifest.subject} grade ${manifest.grade}, not ${integration.subject} grade ${integration.grade}.`);
|
|
94202
|
+
}
|
|
94203
|
+
}
|
|
94204
|
+
function associationMetadataMatches(row, entry) {
|
|
94205
|
+
return row.purpose === entry.purpose && (row.standardFramework ?? undefined) === entry.standard?.framework && (row.standardIdentifier ?? undefined) === entry.standard?.identifier;
|
|
94206
|
+
}
|
|
94207
|
+
function targetStatusSatisfied(row, targetStatus) {
|
|
94208
|
+
return row.status === targetStatus || targetStatus === "draft" && row.status === "live";
|
|
94209
|
+
}
|
|
94210
|
+
function classifyExistingAssessmentImport(row, entry, targetStatus) {
|
|
94211
|
+
if (!associationMetadataMatches(row, entry)) {
|
|
94212
|
+
return {
|
|
94213
|
+
status: "failed",
|
|
94214
|
+
message: "Already attached with different purpose or standard metadata."
|
|
94215
|
+
};
|
|
94216
|
+
}
|
|
94217
|
+
if (targetStatusSatisfied(row, targetStatus)) {
|
|
94218
|
+
return { status: "skipped", message: `Already attached as ${row.status}.` };
|
|
94219
|
+
}
|
|
94220
|
+
if (row.status === "archived") {
|
|
94221
|
+
return {
|
|
94222
|
+
status: "failed",
|
|
94223
|
+
message: "Already attached as archived and cannot be imported again."
|
|
94224
|
+
};
|
|
94225
|
+
}
|
|
94226
|
+
if (targetStatus === "live" && row.status === "draft") {
|
|
94227
|
+
return {
|
|
94228
|
+
status: "failed",
|
|
94229
|
+
message: "Already attached as a draft. Publish it from the assessment editor before retrying."
|
|
94230
|
+
};
|
|
94231
|
+
}
|
|
94232
|
+
return {
|
|
94233
|
+
status: "failed",
|
|
94234
|
+
message: `Already attached as ${row.status}, which does not match the requested ${targetStatus} status.`
|
|
94235
|
+
};
|
|
94236
|
+
}
|
|
94237
|
+
function attachedAssessmentImportMessage(targetStatus, editable) {
|
|
94238
|
+
if (targetStatus === "live") {
|
|
94239
|
+
return editable ? "Attached live." : "Attached live. QTI content is read-only because another app owns it.";
|
|
94240
|
+
}
|
|
94241
|
+
return editable ? "Attached as a draft." : "Attached as a read-only draft owned by another app.";
|
|
94242
|
+
}
|
|
94243
|
+
function liveReviewAssessmentImportFailures(targetStatus, candidates, existingRows) {
|
|
94244
|
+
if (targetStatus === "draft") {
|
|
94245
|
+
return new Map;
|
|
94246
|
+
}
|
|
94247
|
+
const reviews = candidates.filter((candidate) => candidate.purpose === "review");
|
|
94248
|
+
const hasExistingLiveReview = existingRows.some((row) => row.purpose === "review" && row.status === "live");
|
|
94249
|
+
if (reviews.length <= 1 && !hasExistingLiveReview) {
|
|
94250
|
+
return new Map;
|
|
94251
|
+
}
|
|
94252
|
+
const message = hasExistingLiveReview ? "Another review assessment is already live for this course." : "Only one review assessment can be imported live at a time.";
|
|
94253
|
+
return new Map(reviews.map((review) => [review.qtiTestIdentifier, message]));
|
|
94254
|
+
}
|
|
94255
|
+
var init_timeback_assessment_import_util = __esm(() => {
|
|
94256
|
+
init_errors2();
|
|
94257
|
+
init_timeback_qti_authoring_util();
|
|
94258
|
+
});
|
|
94259
|
+
|
|
94153
94260
|
// ../api-core/src/utils/timeback-qti-library.util.ts
|
|
94154
94261
|
function newPlaycademyTestIdentifier() {
|
|
94155
94262
|
return `${PLAYCADEMY_QTI_SOURCE_PREFIX}${crypto.randomUUID()}`;
|
|
@@ -94297,6 +94404,150 @@ class TimebackAssessmentsService {
|
|
|
94297
94404
|
throw error88;
|
|
94298
94405
|
}
|
|
94299
94406
|
}
|
|
94407
|
+
async attachExistingAssessments(integrationId, manifest) {
|
|
94408
|
+
const client2 = this.requireClient();
|
|
94409
|
+
const { integration, gameSlug } = await this.requireQtiTestOwnershipContext(integrationId);
|
|
94410
|
+
assertAssessmentImportCourseMatches(manifest, integration);
|
|
94411
|
+
validateUniqueAssessmentIdentifiers(manifest.assessments.map((assessment) => assessment.qtiTestIdentifier));
|
|
94412
|
+
const entries = manifest.assessments.map((assessment, index2) => ({
|
|
94413
|
+
...assessment,
|
|
94414
|
+
index: index2,
|
|
94415
|
+
standard: this.requirePurposeStandard(assessment.purpose, assessment.standard)
|
|
94416
|
+
}));
|
|
94417
|
+
const existingRows = await this.deps.db.query.gameTimebackAssessmentTests.findMany({
|
|
94418
|
+
where: eq(gameTimebackAssessmentTests.integrationId, integrationId)
|
|
94419
|
+
});
|
|
94420
|
+
const existingByIdentifier = new Map(existingRows.map((row) => [row.qtiTestIdentifier, row]));
|
|
94421
|
+
const validated = await runWithConcurrency(entries, QTI_HYDRATION_CONCURRENCY, async (entry) => {
|
|
94422
|
+
try {
|
|
94423
|
+
const loaded = await loadHydratedQtiTest(client2, entry.qtiTestIdentifier);
|
|
94424
|
+
assertAssessmentHasQuestions(loaded.questions.questions);
|
|
94425
|
+
assertPlayableAssessmentImportQuestions(manifest.targetStatus, loaded.questions.questions.map(({ question }) => question));
|
|
94426
|
+
if (entry.purpose === "review") {
|
|
94427
|
+
assertReviewAssessmentHasStandards(loaded.questions.questions.map(({ question }) => qtiAssessmentStandardRefsFromMetadata2(question.metadata).filter((standard) => canonicalReviewStandardRef(standard) !== null).length));
|
|
94428
|
+
}
|
|
94429
|
+
return { entry, test: loaded.test };
|
|
94430
|
+
} catch (error88) {
|
|
94431
|
+
return { entry, error: error88 };
|
|
94432
|
+
}
|
|
94433
|
+
});
|
|
94434
|
+
const results = [];
|
|
94435
|
+
const pending = [];
|
|
94436
|
+
for (const validation of validated) {
|
|
94437
|
+
const { entry } = validation;
|
|
94438
|
+
const { index: index2 } = entry;
|
|
94439
|
+
const base = {
|
|
94440
|
+
qtiTestIdentifier: entry.qtiTestIdentifier,
|
|
94441
|
+
purpose: entry.purpose,
|
|
94442
|
+
...entry.standard ? { standard: entry.standard } : {},
|
|
94443
|
+
sourceSortOrder: entry.sortOrder
|
|
94444
|
+
};
|
|
94445
|
+
if ("error" in validation) {
|
|
94446
|
+
results[index2] = {
|
|
94447
|
+
...base,
|
|
94448
|
+
status: "failed",
|
|
94449
|
+
message: `QTI validation failed: ${errorMessage(validation.error)}`
|
|
94450
|
+
};
|
|
94451
|
+
} else {
|
|
94452
|
+
const existing = existingByIdentifier.get(entry.qtiTestIdentifier);
|
|
94453
|
+
const editable = isQtiTestOwnedByGame(validation.test, gameSlug);
|
|
94454
|
+
const hydratedBase = { ...base, title: validation.test.title, editable };
|
|
94455
|
+
if (!existing) {
|
|
94456
|
+
pending.push({ entry, test: validation.test });
|
|
94457
|
+
} else {
|
|
94458
|
+
const decision = classifyExistingAssessmentImport(existing, {
|
|
94459
|
+
purpose: entry.purpose,
|
|
94460
|
+
...entry.standard ? { standard: entry.standard } : {}
|
|
94461
|
+
}, manifest.targetStatus);
|
|
94462
|
+
results[index2] = {
|
|
94463
|
+
...hydratedBase,
|
|
94464
|
+
status: decision.status,
|
|
94465
|
+
association: this.associationImportSummary(existing),
|
|
94466
|
+
message: decision.message
|
|
94467
|
+
};
|
|
94468
|
+
}
|
|
94469
|
+
}
|
|
94470
|
+
}
|
|
94471
|
+
const liveReviewFailures = liveReviewAssessmentImportFailures(manifest.targetStatus, pending.map(({ entry }) => entry), existingRows);
|
|
94472
|
+
for (const { entry, test } of pending) {
|
|
94473
|
+
const message = liveReviewFailures.get(entry.qtiTestIdentifier);
|
|
94474
|
+
if (message) {
|
|
94475
|
+
results[entry.index] = {
|
|
94476
|
+
qtiTestIdentifier: entry.qtiTestIdentifier,
|
|
94477
|
+
title: test.title,
|
|
94478
|
+
purpose: entry.purpose,
|
|
94479
|
+
...entry.standard ? { standard: entry.standard } : {},
|
|
94480
|
+
sourceSortOrder: entry.sortOrder,
|
|
94481
|
+
editable: isQtiTestOwnedByGame(test, gameSlug),
|
|
94482
|
+
status: "failed",
|
|
94483
|
+
message
|
|
94484
|
+
};
|
|
94485
|
+
}
|
|
94486
|
+
}
|
|
94487
|
+
const attachablePending = pending.filter(({ entry }) => !liveReviewFailures.has(entry.qtiTestIdentifier));
|
|
94488
|
+
for (const validation of attachablePending) {
|
|
94489
|
+
const { entry, test } = validation;
|
|
94490
|
+
const { index: index2 } = entry;
|
|
94491
|
+
const base = {
|
|
94492
|
+
qtiTestIdentifier: entry.qtiTestIdentifier,
|
|
94493
|
+
title: test.title,
|
|
94494
|
+
purpose: entry.purpose,
|
|
94495
|
+
...entry.standard ? { standard: entry.standard } : {},
|
|
94496
|
+
sourceSortOrder: entry.sortOrder,
|
|
94497
|
+
editable: isQtiTestOwnedByGame(test, gameSlug)
|
|
94498
|
+
};
|
|
94499
|
+
try {
|
|
94500
|
+
const [row] = await this.deps.db.insert(gameTimebackAssessmentTests).values({
|
|
94501
|
+
integrationId,
|
|
94502
|
+
qtiTestIdentifier: entry.qtiTestIdentifier,
|
|
94503
|
+
purpose: entry.purpose,
|
|
94504
|
+
status: manifest.targetStatus,
|
|
94505
|
+
sortOrder: entry.sortOrder,
|
|
94506
|
+
standardFramework: entry.standard?.framework,
|
|
94507
|
+
standardIdentifier: entry.standard?.identifier
|
|
94508
|
+
}).returning();
|
|
94509
|
+
if (!row) {
|
|
94510
|
+
throw new Error("Assessment association create returned no row");
|
|
94511
|
+
}
|
|
94512
|
+
results[index2] = {
|
|
94513
|
+
...base,
|
|
94514
|
+
status: "created",
|
|
94515
|
+
association: this.associationImportSummary(row),
|
|
94516
|
+
message: attachedAssessmentImportMessage(manifest.targetStatus, base.editable)
|
|
94517
|
+
};
|
|
94518
|
+
} catch (error88) {
|
|
94519
|
+
results[index2] = {
|
|
94520
|
+
...base,
|
|
94521
|
+
status: "failed",
|
|
94522
|
+
message: `Association attach failed: ${errorMessage(error88)}`
|
|
94523
|
+
};
|
|
94524
|
+
if (isUniqueViolation(error88)) {
|
|
94525
|
+
let concurrentAssociation;
|
|
94526
|
+
try {
|
|
94527
|
+
concurrentAssociation = await this.deps.db.query.gameTimebackAssessmentTests.findFirst({
|
|
94528
|
+
where: and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.qtiTestIdentifier, entry.qtiTestIdentifier))
|
|
94529
|
+
});
|
|
94530
|
+
} catch {}
|
|
94531
|
+
if (concurrentAssociation) {
|
|
94532
|
+
const decision = classifyExistingAssessmentImport(concurrentAssociation, {
|
|
94533
|
+
purpose: entry.purpose,
|
|
94534
|
+
...entry.standard ? { standard: entry.standard } : {}
|
|
94535
|
+
}, manifest.targetStatus);
|
|
94536
|
+
results[index2] = {
|
|
94537
|
+
...base,
|
|
94538
|
+
status: decision.status,
|
|
94539
|
+
association: this.associationImportSummary(concurrentAssociation),
|
|
94540
|
+
message: decision.message
|
|
94541
|
+
};
|
|
94542
|
+
}
|
|
94543
|
+
}
|
|
94544
|
+
}
|
|
94545
|
+
}
|
|
94546
|
+
setAttribute("app.assessment.operation", "bulk_attach_existing");
|
|
94547
|
+
setAttribute("app.assessment.bulk_attach.target_status", manifest.targetStatus);
|
|
94548
|
+
setAttribute("app.assessment.bulk_attach.created", results.filter((result) => result.status === "created").length);
|
|
94549
|
+
return { results };
|
|
94550
|
+
}
|
|
94300
94551
|
async updateAssessment(integrationId, qtiTestIdentifier, input) {
|
|
94301
94552
|
try {
|
|
94302
94553
|
return await this.withLockedAssessmentAssociations(integrationId, qtiTestIdentifier, async (row, tx, associations) => {
|
|
@@ -94810,6 +95061,13 @@ class TimebackAssessmentsService {
|
|
|
94810
95061
|
updatedAt: row.updatedAt
|
|
94811
95062
|
};
|
|
94812
95063
|
}
|
|
95064
|
+
associationImportSummary(row) {
|
|
95065
|
+
return {
|
|
95066
|
+
...this.associationSummary(row),
|
|
95067
|
+
createdAt: row.createdAt.toISOString(),
|
|
95068
|
+
updatedAt: row.updatedAt.toISOString()
|
|
95069
|
+
};
|
|
95070
|
+
}
|
|
94813
95071
|
canonicalAssessmentStandard(input) {
|
|
94814
95072
|
const standard = canonicalReviewStandardRef(input);
|
|
94815
95073
|
if (!standard) {
|
|
@@ -94856,6 +95114,7 @@ var init_timeback_assessments_service = __esm(async () => {
|
|
|
94856
95114
|
init_qti();
|
|
94857
95115
|
init_timeback3();
|
|
94858
95116
|
init_errors2();
|
|
95117
|
+
init_timeback_assessment_import_util();
|
|
94859
95118
|
init_timeback_assessment_rules_util();
|
|
94860
95119
|
init_timeback_qti_authoring_util();
|
|
94861
95120
|
init_timeback_qti_hydration_util();
|
|
@@ -154836,7 +155095,7 @@ function parseQtiLibraryParams(searchParams) {
|
|
|
154836
155095
|
limit
|
|
154837
155096
|
};
|
|
154838
155097
|
}
|
|
154839
|
-
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, saveRuntimeAssessment, submitRuntimeAssessmentItem, submitRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, updateAssessment, reorderAssessments, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
155098
|
+
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, saveRuntimeAssessment, submitRuntimeAssessmentItem, submitRuntimeAssessment, 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, reorderAssessments, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
154840
155099
|
var init_timeback_controller = __esm(() => {
|
|
154841
155100
|
init_esm();
|
|
154842
155101
|
init_src();
|
|
@@ -155491,6 +155750,15 @@ var init_timeback_controller = __esm(() => {
|
|
|
155491
155750
|
qtiTestIdentifier
|
|
155492
155751
|
});
|
|
155493
155752
|
});
|
|
155753
|
+
attachExistingAssessments = requireDeveloper(async (ctx) => {
|
|
155754
|
+
const { gameId, courseId } = ctx.params;
|
|
155755
|
+
if (!gameId || !courseId) {
|
|
155756
|
+
throw ApiError.badRequest("Missing gameId or courseId parameter");
|
|
155757
|
+
}
|
|
155758
|
+
const manifest = await parseRequestBody(ctx.request, AttachExistingAssessmentsRequestSchema);
|
|
155759
|
+
const integrationId = await ctx.services.timebackAssessments.resolveIntegrationId(gameId, courseId, ctx.user);
|
|
155760
|
+
return ctx.services.timebackAssessments.attachExistingAssessments(integrationId, manifest);
|
|
155761
|
+
});
|
|
155494
155762
|
updateAssessment = requireDeveloper(async (ctx) => {
|
|
155495
155763
|
const { gameId, courseId, testIdentifier } = ctx.params;
|
|
155496
155764
|
if (!gameId || !courseId || !testIdentifier) {
|
|
@@ -155640,6 +155908,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
155640
155908
|
reactivateEnrollment,
|
|
155641
155909
|
listAssessments,
|
|
155642
155910
|
createAssessment,
|
|
155911
|
+
attachExistingAssessments,
|
|
155643
155912
|
updateAssessment,
|
|
155644
155913
|
reorderAssessments,
|
|
155645
155914
|
removeAssessment,
|