@playcademy/vite-plugin 1.2.1-beta.24 → 1.2.1-beta.25
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/index.js +563 -183
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -24315,7 +24315,7 @@ import path2 from "node:path";
|
|
|
24315
24315
|
// package.json
|
|
24316
24316
|
var package_default = {
|
|
24317
24317
|
name: "@playcademy/vite-plugin",
|
|
24318
|
-
version: "1.2.1-beta.
|
|
24318
|
+
version: "1.2.1-beta.25",
|
|
24319
24319
|
type: "module",
|
|
24320
24320
|
exports: {
|
|
24321
24321
|
".": {
|
|
@@ -25921,7 +25921,7 @@ var package_default2;
|
|
|
25921
25921
|
var init_package = __esm(() => {
|
|
25922
25922
|
package_default2 = {
|
|
25923
25923
|
name: "@playcademy/sandbox",
|
|
25924
|
-
version: "0.7.1-beta.
|
|
25924
|
+
version: "0.7.1-beta.23",
|
|
25925
25925
|
description: "Local development server for Playcademy game development",
|
|
25926
25926
|
type: "module",
|
|
25927
25927
|
exports: {
|
|
@@ -34376,6 +34376,9 @@ function isAssessmentAttemptOpen(attempt) {
|
|
|
34376
34376
|
function isAssessmentAttemptCompleted(attempt) {
|
|
34377
34377
|
return attempt.inProgress === ASSESSMENT_ATTEMPT_COMPLETED.inProgress && attempt.scoreStatus === ASSESSMENT_ATTEMPT_COMPLETED.scoreStatus;
|
|
34378
34378
|
}
|
|
34379
|
+
function isAssessmentAttemptSuperseded(attempt) {
|
|
34380
|
+
return attempt.inProgress === ASSESSMENT_ATTEMPT_SUPERSEDED.inProgress && attempt.scoreStatus === ASSESSMENT_ATTEMPT_SUPERSEDED.scoreStatus;
|
|
34381
|
+
}
|
|
34379
34382
|
function classifyAssessmentSubmission(attempt, submissionId) {
|
|
34380
34383
|
if (isAssessmentAttemptCompleted(attempt)) {
|
|
34381
34384
|
return attempt.submissionId === submissionId ? "replay" : "reject";
|
|
@@ -34433,6 +34436,15 @@ function assessmentReviewBankShortage(fulfillment) {
|
|
|
34433
34436
|
function assessmentFlowForPurpose(purpose) {
|
|
34434
34437
|
return purpose === "review" ? "item-submit" : "attempt-submit";
|
|
34435
34438
|
}
|
|
34439
|
+
function compareCodeUnits(left, right) {
|
|
34440
|
+
if (left < right) {
|
|
34441
|
+
return -1;
|
|
34442
|
+
}
|
|
34443
|
+
if (left > right) {
|
|
34444
|
+
return 1;
|
|
34445
|
+
}
|
|
34446
|
+
return 0;
|
|
34447
|
+
}
|
|
34436
34448
|
function canonicalJson(value) {
|
|
34437
34449
|
if (value === null || typeof value !== "object") {
|
|
34438
34450
|
return JSON.stringify(value) ?? "null";
|
|
@@ -34440,18 +34452,9 @@ function canonicalJson(value) {
|
|
|
34440
34452
|
if (Array.isArray(value)) {
|
|
34441
34453
|
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
34442
34454
|
}
|
|
34443
|
-
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined).toSorted(([left], [right]) =>
|
|
34455
|
+
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined).toSorted(([left], [right]) => compareCodeUnits(left, right)).map(([key, entryValue]) => `${JSON.stringify(key)}:${canonicalJson(entryValue)}`);
|
|
34444
34456
|
return `{${entries.join(",")}}`;
|
|
34445
34457
|
}
|
|
34446
|
-
function compareCanonicalKeys(left, right) {
|
|
34447
|
-
if (left < right) {
|
|
34448
|
-
return -1;
|
|
34449
|
-
}
|
|
34450
|
-
if (left > right) {
|
|
34451
|
-
return 1;
|
|
34452
|
-
}
|
|
34453
|
-
return 0;
|
|
34454
|
-
}
|
|
34455
34458
|
async function diagnosticRoutingRevision(manifest) {
|
|
34456
34459
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(manifest)));
|
|
34457
34460
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
@@ -35751,15 +35754,6 @@ function assessmentPresentationForAttempt(assessment, attemptId) {
|
|
|
35751
35754
|
});
|
|
35752
35755
|
return { ...assessment, items };
|
|
35753
35756
|
}
|
|
35754
|
-
function compareCodeUnits(left, right) {
|
|
35755
|
-
if (left < right) {
|
|
35756
|
-
return -1;
|
|
35757
|
-
}
|
|
35758
|
-
if (left > right) {
|
|
35759
|
-
return 1;
|
|
35760
|
-
}
|
|
35761
|
-
return 0;
|
|
35762
|
-
}
|
|
35763
35757
|
function reviewStandardFieldsWithinLimits(input) {
|
|
35764
35758
|
return input.framework.trim().length <= TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength && input.identifier.trim().length <= TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength;
|
|
35765
35759
|
}
|
|
@@ -35770,6 +35764,19 @@ function canonicalReviewStandardRef(input) {
|
|
|
35770
35764
|
}
|
|
35771
35765
|
return standard.framework && standard.identifier ? standard : null;
|
|
35772
35766
|
}
|
|
35767
|
+
function isKnownDescriptiveCurriculumFramework(framework) {
|
|
35768
|
+
return framework === "CCSS" || framework.startsWith("CCSS.");
|
|
35769
|
+
}
|
|
35770
|
+
function reviewRoutingStandardRefsFromMetadata(metadata2, options = {}) {
|
|
35771
|
+
const refs = new Map;
|
|
35772
|
+
for (const authored of qtiAssessmentStandardRefsFromMetadata(metadata2)) {
|
|
35773
|
+
const standard = canonicalReviewStandardRef(authored);
|
|
35774
|
+
if (standard && (!options.customOnly || !isKnownDescriptiveCurriculumFramework(standard.framework))) {
|
|
35775
|
+
refs.set(assessmentStandardRefKey(standard), standard);
|
|
35776
|
+
}
|
|
35777
|
+
}
|
|
35778
|
+
return [...refs.values()];
|
|
35779
|
+
}
|
|
35773
35780
|
function canonicalRequestStandards(standards) {
|
|
35774
35781
|
if (standards.length > TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standards) {
|
|
35775
35782
|
throw new RangeError(`A review request may contain at most ${TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standards} standards`);
|
|
@@ -35825,42 +35832,172 @@ function qtiQuestionByIdentifier(questions) {
|
|
|
35825
35832
|
}
|
|
35826
35833
|
return byIdentifier;
|
|
35827
35834
|
}
|
|
35828
|
-
async function
|
|
35835
|
+
async function reviewBankRevision(input) {
|
|
35836
|
+
const alignmentSignature = input.items.map((item) => ({
|
|
35837
|
+
itemIdentifier: item.itemIdentifier,
|
|
35838
|
+
standardKeys: item.standards.map(assessmentStandardRefKey)
|
|
35839
|
+
}));
|
|
35840
|
+
const bankRevisionUuid = await deterministicUUID(`playcademy:review-bank:v1:${JSON.stringify({
|
|
35841
|
+
bankIdentifier: input.bankIdentifier,
|
|
35842
|
+
contentRevision: input.sourceContentRevision,
|
|
35843
|
+
alignments: alignmentSignature
|
|
35844
|
+
})}`);
|
|
35845
|
+
return `review-bank-v1:${bankRevisionUuid}`;
|
|
35846
|
+
}
|
|
35847
|
+
async function reviewBankIndex(input) {
|
|
35848
|
+
return {
|
|
35849
|
+
...input,
|
|
35850
|
+
bankRevision: await reviewBankRevision(input)
|
|
35851
|
+
};
|
|
35852
|
+
}
|
|
35853
|
+
async function reviewBankSourceFingerprint(input) {
|
|
35854
|
+
const fingerprintUuid = await deterministicUUID(`playcademy:review-bank-source:v1:${JSON.stringify({
|
|
35855
|
+
bankIdentifier: input.bankIdentifier,
|
|
35856
|
+
items: input.items.map((item) => ({
|
|
35857
|
+
itemIdentifier: item.itemIdentifier,
|
|
35858
|
+
standardKeys: item.standards.map(assessmentStandardRefKey)
|
|
35859
|
+
}))
|
|
35860
|
+
})}`);
|
|
35861
|
+
return `review-bank-source-v1:${fingerprintUuid}`;
|
|
35862
|
+
}
|
|
35863
|
+
async function buildReviewBankIndex(assessment, questions, options = {}) {
|
|
35829
35864
|
if (assessment.identifier !== questions.assessmentTest) {
|
|
35830
35865
|
throw new Error(`QTI questions for ${questions.assessmentTest} do not describe assessment ${assessment.identifier}`);
|
|
35831
35866
|
}
|
|
35832
35867
|
const questionByIdentifier = qtiQuestionByIdentifier(questions);
|
|
35833
35868
|
const items = assessment.items.map((item) => {
|
|
35834
35869
|
const question = questionByIdentifier.get(item.identifier);
|
|
35835
|
-
const
|
|
35836
|
-
for (const authored of qtiAssessmentStandardRefsFromMetadata(question?.metadata)) {
|
|
35837
|
-
const standard = canonicalReviewStandardRef(authored);
|
|
35838
|
-
if (standard) {
|
|
35839
|
-
canonical.set(assessmentStandardRefKey(standard), standard);
|
|
35840
|
-
}
|
|
35841
|
-
}
|
|
35842
|
-
const standards = [...canonical.values()].toSorted((left, right) => compareCodeUnits(assessmentStandardRefKey(left), assessmentStandardRefKey(right)));
|
|
35870
|
+
const standards = reviewRoutingStandardRefsFromMetadata(question?.metadata, options).toSorted((left, right) => compareCodeUnits(assessmentStandardRefKey(left), assessmentStandardRefKey(right)));
|
|
35843
35871
|
return {
|
|
35844
35872
|
itemIdentifier: item.identifier,
|
|
35845
35873
|
standards
|
|
35846
35874
|
};
|
|
35847
35875
|
});
|
|
35848
|
-
|
|
35849
|
-
itemIdentifier: item.itemIdentifier,
|
|
35850
|
-
standardKeys: item.standards.map(assessmentStandardRefKey)
|
|
35851
|
-
}));
|
|
35852
|
-
const bankRevisionUuid = await deterministicUUID(`playcademy:review-bank:v1:${JSON.stringify({
|
|
35853
|
-
bankIdentifier: assessment.identifier,
|
|
35854
|
-
contentRevision: assessment.contentRevision,
|
|
35855
|
-
alignments: alignmentSignature
|
|
35856
|
-
})}`);
|
|
35857
|
-
return {
|
|
35876
|
+
return reviewBankIndex({
|
|
35858
35877
|
bankIdentifier: assessment.identifier,
|
|
35859
35878
|
sourceContentRevision: assessment.contentRevision,
|
|
35860
|
-
bankRevision: `review-bank-v1:${bankRevisionUuid}`,
|
|
35861
35879
|
items
|
|
35880
|
+
});
|
|
35881
|
+
}
|
|
35882
|
+
function emptyReviewBankManifest(bankIdentifier) {
|
|
35883
|
+
return {
|
|
35884
|
+
version: PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION,
|
|
35885
|
+
bankIdentifier,
|
|
35886
|
+
sourceFingerprint: null,
|
|
35887
|
+
sourceContentRevision: null,
|
|
35888
|
+
bankRevision: null,
|
|
35889
|
+
itemsByStandard: {}
|
|
35890
|
+
};
|
|
35891
|
+
}
|
|
35892
|
+
function reviewBankMappingIssues(bank) {
|
|
35893
|
+
return {
|
|
35894
|
+
missingOrInvalidItemIdentifiers: bank.items.filter((item) => item.standards.length === 0).map((item) => item.itemIdentifier),
|
|
35895
|
+
multipleStandardItemIdentifiers: bank.items.filter((item) => item.standards.length > 1).map((item) => item.itemIdentifier)
|
|
35896
|
+
};
|
|
35897
|
+
}
|
|
35898
|
+
async function buildReviewBankManifest(bank) {
|
|
35899
|
+
const itemsByStandard = new Map;
|
|
35900
|
+
for (const item of bank.items) {
|
|
35901
|
+
for (const standard of item.standards) {
|
|
35902
|
+
const key = assessmentStandardRefKey(standard);
|
|
35903
|
+
const identifiers = itemsByStandard.get(key) ?? [];
|
|
35904
|
+
identifiers.push(item.itemIdentifier);
|
|
35905
|
+
itemsByStandard.set(key, identifiers);
|
|
35906
|
+
}
|
|
35907
|
+
}
|
|
35908
|
+
return {
|
|
35909
|
+
version: PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION,
|
|
35910
|
+
bankIdentifier: bank.bankIdentifier,
|
|
35911
|
+
sourceFingerprint: await reviewBankSourceFingerprint(bank),
|
|
35912
|
+
sourceContentRevision: bank.sourceContentRevision,
|
|
35913
|
+
bankRevision: bank.bankRevision,
|
|
35914
|
+
itemsByStandard: Object.fromEntries([...itemsByStandard.entries()].toSorted(([left], [right]) => compareCodeUnits(left, right)))
|
|
35862
35915
|
};
|
|
35863
35916
|
}
|
|
35917
|
+
function requiredManifestString(record, field) {
|
|
35918
|
+
const value = record[field];
|
|
35919
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
35920
|
+
throw new Error(`Review-bank manifest requires ${field}`);
|
|
35921
|
+
}
|
|
35922
|
+
return value.trim();
|
|
35923
|
+
}
|
|
35924
|
+
function standardFromManifestKey(key) {
|
|
35925
|
+
try {
|
|
35926
|
+
const parsed = JSON.parse(key);
|
|
35927
|
+
if (!Array.isArray(parsed) || parsed.length !== 2 || typeof parsed[0] !== "string" || typeof parsed[1] !== "string") {
|
|
35928
|
+
return null;
|
|
35929
|
+
}
|
|
35930
|
+
const standard = canonicalReviewStandardRef({
|
|
35931
|
+
framework: parsed[0],
|
|
35932
|
+
identifier: parsed[1]
|
|
35933
|
+
});
|
|
35934
|
+
return standard && assessmentStandardRefKey(standard) === key ? standard : null;
|
|
35935
|
+
} catch {
|
|
35936
|
+
return null;
|
|
35937
|
+
}
|
|
35938
|
+
}
|
|
35939
|
+
async function reviewBankIndexFromManifest(metadata2, input) {
|
|
35940
|
+
const raw = isRecord(metadata2) ? metadata2[PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY] : undefined;
|
|
35941
|
+
if (raw === undefined) {
|
|
35942
|
+
return null;
|
|
35943
|
+
}
|
|
35944
|
+
if (!isRecord(raw) || raw.version !== PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION) {
|
|
35945
|
+
throw new Error("Review-bank manifest has an unsupported version or shape");
|
|
35946
|
+
}
|
|
35947
|
+
const manifest = raw;
|
|
35948
|
+
const bankIdentifier = requiredManifestString(manifest, "bankIdentifier");
|
|
35949
|
+
if (bankIdentifier !== input.bankIdentifier) {
|
|
35950
|
+
throw new Error(`Review-bank manifest for ${bankIdentifier} does not describe ${input.bankIdentifier}`);
|
|
35951
|
+
}
|
|
35952
|
+
if (manifest.sourceFingerprint === null && manifest.sourceContentRevision === null && manifest.bankRevision === null) {
|
|
35953
|
+
return null;
|
|
35954
|
+
}
|
|
35955
|
+
const sourceFingerprint = requiredManifestString(manifest, "sourceFingerprint");
|
|
35956
|
+
const sourceContentRevision = requiredManifestString(manifest, "sourceContentRevision");
|
|
35957
|
+
const declaredBankRevision = requiredManifestString(manifest, "bankRevision");
|
|
35958
|
+
const itemsByStandard = manifest.itemsByStandard;
|
|
35959
|
+
if (!isRecord(itemsByStandard)) {
|
|
35960
|
+
throw new Error("Review-bank manifest requires an itemsByStandard mapping");
|
|
35961
|
+
}
|
|
35962
|
+
const membership = new Set(input.membershipItemIdentifiers);
|
|
35963
|
+
const standardsByItem = new Map;
|
|
35964
|
+
for (const [standardKey, rawItemIdentifiers] of Object.entries(itemsByStandard)) {
|
|
35965
|
+
const standard = standardFromManifestKey(standardKey);
|
|
35966
|
+
if (!standard || !Array.isArray(rawItemIdentifiers)) {
|
|
35967
|
+
throw new Error("Review-bank manifest contains an invalid standard mapping");
|
|
35968
|
+
}
|
|
35969
|
+
for (const rawItemIdentifier of rawItemIdentifiers) {
|
|
35970
|
+
if (typeof rawItemIdentifier !== "string" || !rawItemIdentifier.trim()) {
|
|
35971
|
+
throw new Error("Review-bank manifest contains an invalid item identifier");
|
|
35972
|
+
}
|
|
35973
|
+
const itemIdentifier = rawItemIdentifier.trim();
|
|
35974
|
+
if (membership.has(itemIdentifier)) {
|
|
35975
|
+
const standards = standardsByItem.get(itemIdentifier) ?? new Map;
|
|
35976
|
+
standards.set(assessmentStandardRefKey(standard), standard);
|
|
35977
|
+
standardsByItem.set(itemIdentifier, standards);
|
|
35978
|
+
}
|
|
35979
|
+
}
|
|
35980
|
+
}
|
|
35981
|
+
const index = await reviewBankIndex({
|
|
35982
|
+
bankIdentifier,
|
|
35983
|
+
sourceContentRevision,
|
|
35984
|
+
items: input.membershipItemIdentifiers.map((itemIdentifier) => ({
|
|
35985
|
+
itemIdentifier,
|
|
35986
|
+
standards: [...standardsByItem.get(itemIdentifier)?.values() ?? []].toSorted((left, right) => compareCodeUnits(assessmentStandardRefKey(left), assessmentStandardRefKey(right)))
|
|
35987
|
+
}))
|
|
35988
|
+
});
|
|
35989
|
+
const issues = reviewBankMappingIssues(index);
|
|
35990
|
+
if (issues.missingOrInvalidItemIdentifiers.length > 0 || issues.multipleStandardItemIdentifiers.length > 0) {
|
|
35991
|
+
throw new Error("Review-bank manifest must map every member to exactly one standard/node key");
|
|
35992
|
+
}
|
|
35993
|
+
if (await reviewBankSourceFingerprint(index) !== sourceFingerprint) {
|
|
35994
|
+
throw new Error("Review-bank manifest source fingerprint is stale");
|
|
35995
|
+
}
|
|
35996
|
+
if (index.bankRevision !== declaredBankRevision) {
|
|
35997
|
+
throw new Error("Review-bank manifest revision does not match its candidate index");
|
|
35998
|
+
}
|
|
35999
|
+
return index;
|
|
36000
|
+
}
|
|
35864
36001
|
function exposureTimestamp(value) {
|
|
35865
36002
|
const timestamp = Date.parse(value);
|
|
35866
36003
|
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
@@ -36348,6 +36485,7 @@ var COMMON_CORE_ELA_FRAMEWORK_ALIASES;
|
|
|
36348
36485
|
var COMMON_CORE_FRAMEWORK_ALIASES;
|
|
36349
36486
|
var ASSESSMENT_ATTEMPT_OPEN;
|
|
36350
36487
|
var ASSESSMENT_ATTEMPT_COMPLETED;
|
|
36488
|
+
var ASSESSMENT_ATTEMPT_SUPERSEDED;
|
|
36351
36489
|
var ASSESSMENT_RUNTIME_ERROR_STATUS;
|
|
36352
36490
|
var ROUTING_KEY_MAX_LENGTH = 128;
|
|
36353
36491
|
var ROUTING_RESULT_KEY_MAX_LENGTH = 256;
|
|
@@ -36367,6 +36505,8 @@ var DiagnosticRoutingManifestV1Schema;
|
|
|
36367
36505
|
var GRADE_VALUES;
|
|
36368
36506
|
var POINT_RESPONSE_PATTERN;
|
|
36369
36507
|
var REVIEW_SELECTION_POLICY_VERSION = "unseen-first-least-recently-delivered-v1";
|
|
36508
|
+
var PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY = "playcademyReviewBank";
|
|
36509
|
+
var PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION = 1;
|
|
36370
36510
|
var DEFAULT_REVIEW_SELECTION_POLICY;
|
|
36371
36511
|
var RuntimeSubjectSchema;
|
|
36372
36512
|
var RuntimeGradeSchema;
|
|
@@ -36571,6 +36711,10 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
36571
36711
|
inProgress: "false",
|
|
36572
36712
|
scoreStatus: "fully graded"
|
|
36573
36713
|
};
|
|
36714
|
+
ASSESSMENT_ATTEMPT_SUPERSEDED = {
|
|
36715
|
+
inProgress: "false",
|
|
36716
|
+
scoreStatus: "not submitted"
|
|
36717
|
+
};
|
|
36574
36718
|
ASSESSMENT_RUNTIME_ERROR_STATUS = {
|
|
36575
36719
|
NO_ELIGIBLE_TESTS: 404,
|
|
36576
36720
|
REVIEW_BANK_SHORTAGE: 422,
|
|
@@ -115025,73 +115169,6 @@ function stringField2(value) {
|
|
|
115025
115169
|
function firstStringField2(...values) {
|
|
115026
115170
|
return values.map(stringField2).find(Boolean) ?? "";
|
|
115027
115171
|
}
|
|
115028
|
-
function normalizedWhitespace2(value) {
|
|
115029
|
-
return value.normalize("NFKC").trim().replace(/\s+/g, " ");
|
|
115030
|
-
}
|
|
115031
|
-
function normalizedIdentityCase2(value) {
|
|
115032
|
-
return value.toLocaleUpperCase("en-US");
|
|
115033
|
-
}
|
|
115034
|
-
function frameworkAliasKey2(value) {
|
|
115035
|
-
return value.normalize("NFKC").toLocaleLowerCase("en-US").replace(/[^a-z0-9]+/g, "");
|
|
115036
|
-
}
|
|
115037
|
-
function isUuidShaped2(value) {
|
|
115038
|
-
return UUID_PATTERN2.test(value.trim());
|
|
115039
|
-
}
|
|
115040
|
-
function isCommonCoreMathIdentifier2(identifier) {
|
|
115041
|
-
const canonical = normalizedIdentityCase2(normalizedWhitespace2(identifier));
|
|
115042
|
-
return canonical.startsWith("CCSS.MATH.") || /^(?:K|[1-8])\.(?:CC|OA|NBT|NF|MD|RP|NS|EE|F|G|SP)\./.test(canonical) || /^(?:HS)?(?:N|A|F|G|S)-[A-Z]+\./.test(canonical) || /^MP\.?\d/.test(canonical);
|
|
115043
|
-
}
|
|
115044
|
-
function isCommonCoreElaIdentifier2(identifier) {
|
|
115045
|
-
const canonical = normalizedIdentityCase2(normalizedWhitespace2(identifier));
|
|
115046
|
-
return canonical.startsWith("CCSS.ELA-LITERACY.") || /^(?:RL|RI|RF|W|SL|L)\.(?:K|[1-9]|1[0-2])\./.test(canonical) || /^(?:RH|RST|WHST)\.(?:6-8|9-10|11-12)\./.test(canonical) || /^CCRA\.(?:R|W|SL|L)\./.test(canonical);
|
|
115047
|
-
}
|
|
115048
|
-
function canonicalFramework2(authoredFramework, identifier) {
|
|
115049
|
-
const aliasKey = frameworkAliasKey2(authoredFramework);
|
|
115050
|
-
if (COMMON_CORE_MATH_FRAMEWORK_ALIASES2.has(aliasKey) || COMMON_CORE_FRAMEWORK_ALIASES2.has(aliasKey) && isCommonCoreMathIdentifier2(identifier)) {
|
|
115051
|
-
return "CCSS.Math";
|
|
115052
|
-
}
|
|
115053
|
-
if (COMMON_CORE_ELA_FRAMEWORK_ALIASES2.has(aliasKey) || COMMON_CORE_FRAMEWORK_ALIASES2.has(aliasKey) && isCommonCoreElaIdentifier2(identifier)) {
|
|
115054
|
-
return "CCSS.ELA-Literacy";
|
|
115055
|
-
}
|
|
115056
|
-
if (COMMON_CORE_FRAMEWORK_ALIASES2.has(aliasKey)) {
|
|
115057
|
-
return "CCSS";
|
|
115058
|
-
}
|
|
115059
|
-
return normalizedIdentityCase2(authoredFramework);
|
|
115060
|
-
}
|
|
115061
|
-
function canonicalIdentifier2(framework, authoredIdentifier) {
|
|
115062
|
-
const identifier = normalizedIdentityCase2(normalizedWhitespace2(authoredIdentifier));
|
|
115063
|
-
if (framework === "CCSS.Math") {
|
|
115064
|
-
return identifier.replace(/^CCSS\.MATH\.CONTENT\./, "").replace(/^CCSS\.MATH\.PRACTICE\./, "").replace(/^CCSS\.MATH\./, "");
|
|
115065
|
-
}
|
|
115066
|
-
if (framework === "CCSS.ELA-Literacy") {
|
|
115067
|
-
return identifier.replace(/^CCSS\.ELA-LITERACY\./, "");
|
|
115068
|
-
}
|
|
115069
|
-
return identifier;
|
|
115070
|
-
}
|
|
115071
|
-
function readableAlignmentIdentifier2(alignment) {
|
|
115072
|
-
const identifier = alignment.identifier?.trim() ?? "";
|
|
115073
|
-
if (identifier && !isUuidShaped2(identifier)) {
|
|
115074
|
-
return identifier;
|
|
115075
|
-
}
|
|
115076
|
-
const id = alignment.id.trim();
|
|
115077
|
-
return id && !isUuidShaped2(id) ? id : "";
|
|
115078
|
-
}
|
|
115079
|
-
function canonicalAssessmentStandardRef2(standard) {
|
|
115080
|
-
const authoredFramework = normalizedWhitespace2(standard.framework);
|
|
115081
|
-
const framework = canonicalFramework2(authoredFramework, standard.identifier);
|
|
115082
|
-
const identifier = canonicalIdentifier2(framework, standard.identifier);
|
|
115083
|
-
return {
|
|
115084
|
-
framework,
|
|
115085
|
-
identifier
|
|
115086
|
-
};
|
|
115087
|
-
}
|
|
115088
|
-
function assessmentStandardRefKey2(standard) {
|
|
115089
|
-
const canonical = canonicalAssessmentStandardRef2(standard);
|
|
115090
|
-
return JSON.stringify([
|
|
115091
|
-
normalizedIdentityCase2(canonical.framework),
|
|
115092
|
-
normalizedIdentityCase2(canonical.identifier)
|
|
115093
|
-
]);
|
|
115094
|
-
}
|
|
115095
115172
|
function dedupeStandards2(standards) {
|
|
115096
115173
|
const deduped = new Map;
|
|
115097
115174
|
for (const standard of standards) {
|
|
@@ -115182,22 +115259,6 @@ function qtiStandardsFromMetadata(metadata2) {
|
|
|
115182
115259
|
standards.push(...qtiAlignmentStandardsFromMetadata2(metadata2));
|
|
115183
115260
|
return dedupeStandards2(standards);
|
|
115184
115261
|
}
|
|
115185
|
-
function qtiAssessmentStandardRefsFromMetadata2(metadata2) {
|
|
115186
|
-
const refs = new Map;
|
|
115187
|
-
for (const alignment of qtiAlignmentStandardsFromMetadata2(metadata2)) {
|
|
115188
|
-
const identifier = readableAlignmentIdentifier2(alignment);
|
|
115189
|
-
if (identifier) {
|
|
115190
|
-
const canonical = canonicalAssessmentStandardRef2({
|
|
115191
|
-
framework: alignment.source,
|
|
115192
|
-
identifier
|
|
115193
|
-
});
|
|
115194
|
-
if (canonical.framework && canonical.identifier) {
|
|
115195
|
-
refs.set(assessmentStandardRefKey2(canonical), canonical);
|
|
115196
|
-
}
|
|
115197
|
-
}
|
|
115198
|
-
}
|
|
115199
|
-
return [...refs.values()];
|
|
115200
|
-
}
|
|
115201
115262
|
var EVENT_HANDLER_ATTRIBUTE;
|
|
115202
115263
|
var SCRIPT_SCHEMES2;
|
|
115203
115264
|
var DATA_RASTER_IMAGE_PATTERN2;
|
|
@@ -115242,7 +115303,6 @@ var BLANK_SENTINEL22 = "
|
|
|
115242
115303
|
var PLAYABLE_POINT_VALUE;
|
|
115243
115304
|
var POINT_INTERACTION_TYPES2;
|
|
115244
115305
|
var INDEPENDENT_PROCESSING_TAGS2;
|
|
115245
|
-
var UUID_PATTERN2;
|
|
115246
115306
|
var COMMON_CORE_MATH_FRAMEWORK_ALIASES2;
|
|
115247
115307
|
var COMMON_CORE_ELA_FRAMEWORK_ALIASES2;
|
|
115248
115308
|
var COMMON_CORE_FRAMEWORK_ALIASES2;
|
|
@@ -115495,7 +115555,6 @@ var init_qti = __esm(() => {
|
|
|
115495
115555
|
"qti-sum",
|
|
115496
115556
|
"qti-base-value"
|
|
115497
115557
|
]);
|
|
115498
|
-
UUID_PATTERN2 = /^(?:urn:uuid:)?\{?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}?$/i;
|
|
115499
115558
|
COMMON_CORE_MATH_FRAMEWORK_ALIASES2 = new Set([
|
|
115500
115559
|
"ccssm",
|
|
115501
115560
|
"ccssmath",
|
|
@@ -117895,8 +117954,8 @@ function assertAssessmentHasQuestions(questions) {
|
|
|
117895
117954
|
}
|
|
117896
117955
|
}
|
|
117897
117956
|
function assertReviewAssessmentHasStandards(standardCounts) {
|
|
117898
|
-
if (standardCounts.some((count) => count
|
|
117899
|
-
throw new ValidationError("Every question in a review assessment must have
|
|
117957
|
+
if (standardCounts.some((count) => count !== 1)) {
|
|
117958
|
+
throw new ValidationError("Every question in a review assessment must have exactly one standards alignment");
|
|
117900
117959
|
}
|
|
117901
117960
|
}
|
|
117902
117961
|
function planAssessmentRemoval(status) {
|
|
@@ -117959,6 +118018,41 @@ function orderLiveAssessmentRows(liveRows, purpose, testIdentifiers) {
|
|
|
117959
118018
|
var init_timeback_assessment_rules_util = __esm(() => {
|
|
117960
118019
|
init_errors2();
|
|
117961
118020
|
});
|
|
118021
|
+
function stageAssessmentAttemptSupersession(attempt) {
|
|
118022
|
+
Object.assign(attempt.result, ASSESSMENT_ATTEMPT_SUPERSEDED);
|
|
118023
|
+
Object.assign(attempt.selection, ASSESSMENT_ATTEMPT_SUPERSEDED);
|
|
118024
|
+
}
|
|
118025
|
+
function shouldConfirmAssessmentVersionUnavailable(details) {
|
|
118026
|
+
return typeof details === "object" && details !== null && "expectedRevision" in details && typeof details.expectedRevision === "string" && !("currentRevision" in details);
|
|
118027
|
+
}
|
|
118028
|
+
function buildAssessmentAttemptSupersessionUpdate(result) {
|
|
118029
|
+
if (result.status !== "active" || !isAssessmentAttemptOpen({
|
|
118030
|
+
inProgress: result.inProgress ?? "",
|
|
118031
|
+
scoreStatus: result.scoreStatus
|
|
118032
|
+
})) {
|
|
118033
|
+
return null;
|
|
118034
|
+
}
|
|
118035
|
+
return {
|
|
118036
|
+
status: "active",
|
|
118037
|
+
assessmentLineItem: result.assessmentLineItem,
|
|
118038
|
+
student: result.student,
|
|
118039
|
+
score: result.score ?? 0,
|
|
118040
|
+
scoreDate: result.scoreDate,
|
|
118041
|
+
...ASSESSMENT_ATTEMPT_SUPERSEDED,
|
|
118042
|
+
metadata: result.metadata ?? {}
|
|
118043
|
+
};
|
|
118044
|
+
}
|
|
118045
|
+
async function resolveAvailableAssessmentAttempt(initialDecision, select3, resume) {
|
|
118046
|
+
let decision = initialDecision;
|
|
118047
|
+
while (decision?.kind === "resume") {
|
|
118048
|
+
const snapshot = await resume(decision);
|
|
118049
|
+
if (snapshot !== null) {
|
|
118050
|
+
return { kind: "resumed", snapshot };
|
|
118051
|
+
}
|
|
118052
|
+
decision = select3();
|
|
118053
|
+
}
|
|
118054
|
+
return { kind: "continue", decision };
|
|
118055
|
+
}
|
|
117962
118056
|
function buildReviewItemResultMetadata(input) {
|
|
117963
118057
|
const administration = itemAdministration(input.metadata.itemSubmissions, input.selection.itemIdentifier);
|
|
117964
118058
|
if (!administration) {
|
|
@@ -118403,8 +118497,8 @@ function parsePlayableItem(question) {
|
|
|
118403
118497
|
function assessmentContentRevision(test, questions) {
|
|
118404
118498
|
return sha256Hex(canonicalJson2({
|
|
118405
118499
|
identifier: test.identifier,
|
|
118500
|
+
title: test.title,
|
|
118406
118501
|
qtiVersion: test.qtiVersion,
|
|
118407
|
-
rawXml: test.rawXml,
|
|
118408
118502
|
parts: test["qti-test-part"],
|
|
118409
118503
|
questions: questions.questions.map(({ reference, question }) => ({
|
|
118410
118504
|
identifier: reference.identifier,
|
|
@@ -118560,11 +118654,30 @@ async function hydrateQtiTestQuestions(client2, references) {
|
|
|
118560
118654
|
}));
|
|
118561
118655
|
return { ...references, questions };
|
|
118562
118656
|
}
|
|
118563
|
-
async function
|
|
118657
|
+
async function hydrateQtiTestQuestionSelection(client2, references, itemIdentifiers) {
|
|
118658
|
+
const referenceByIdentifier = new Map(references.questions.map((reference) => [reference.reference.identifier, reference]));
|
|
118659
|
+
const selectedReferences = itemIdentifiers.map((itemIdentifier) => {
|
|
118660
|
+
const reference = referenceByIdentifier.get(itemIdentifier);
|
|
118661
|
+
if (!reference) {
|
|
118662
|
+
throw new Error(`QTI assessment ${references.assessmentTest} does not contain item ${itemIdentifier}`);
|
|
118663
|
+
}
|
|
118664
|
+
return reference;
|
|
118665
|
+
});
|
|
118666
|
+
return hydrateQtiTestQuestions(client2, {
|
|
118667
|
+
...references,
|
|
118668
|
+
totalQuestions: selectedReferences.length,
|
|
118669
|
+
questions: selectedReferences
|
|
118670
|
+
});
|
|
118671
|
+
}
|
|
118672
|
+
async function loadQtiTestReferences(client2, identifier) {
|
|
118564
118673
|
const [test, references] = await Promise.all([
|
|
118565
118674
|
client2.qtiApi.assessmentTests.get(identifier),
|
|
118566
118675
|
client2.qtiApi.assessmentTests.getQuestions(identifier)
|
|
118567
118676
|
]);
|
|
118677
|
+
return { test, references };
|
|
118678
|
+
}
|
|
118679
|
+
async function loadHydratedQtiTest(client2, identifier) {
|
|
118680
|
+
const { test, references } = await loadQtiTestReferences(client2, identifier);
|
|
118568
118681
|
return {
|
|
118569
118682
|
test,
|
|
118570
118683
|
questions: await hydrateQtiTestQuestions(client2, references)
|
|
@@ -118578,6 +118691,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118578
118691
|
static ASSESSMENT_CACHE_TTL_MS = 60000;
|
|
118579
118692
|
static EXPORT_CONCURRENCY = 4;
|
|
118580
118693
|
static ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS = 3;
|
|
118694
|
+
static ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS = 25;
|
|
118581
118695
|
static PROJECTION_BARRIER_MAX_ATTEMPTS = 5;
|
|
118582
118696
|
static PROJECTION_BARRIER_RETRY_DELAY_MS = 25;
|
|
118583
118697
|
static SCORING_CONCURRENCY = 4;
|
|
@@ -118590,6 +118704,10 @@ class TimebackAssessmentRuntimeService {
|
|
|
118590
118704
|
defaultTTL: TimebackAssessmentRuntimeService.ASSESSMENT_CACHE_TTL_MS,
|
|
118591
118705
|
maxSize: TimebackAssessmentRuntimeService.ASSESSMENT_CACHE_LIMIT
|
|
118592
118706
|
});
|
|
118707
|
+
reviewSelectionCache = new TimebackCache({
|
|
118708
|
+
defaultTTL: TimebackAssessmentRuntimeService.ASSESSMENT_CACHE_TTL_MS,
|
|
118709
|
+
maxSize: TimebackAssessmentRuntimeService.ASSESSMENT_CACHE_LIMIT
|
|
118710
|
+
});
|
|
118593
118711
|
constructor(deps) {
|
|
118594
118712
|
this.deps = deps;
|
|
118595
118713
|
}
|
|
@@ -118608,6 +118726,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118608
118726
|
}
|
|
118609
118727
|
const masteryStandard = input.purpose === "mastery" ? normalizeMasteryStandard(input.standard) : undefined;
|
|
118610
118728
|
const requestFingerprint = this.startRequestFingerprint(input);
|
|
118729
|
+
const pendingSupersessions = new Set;
|
|
118611
118730
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
118612
118731
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
118613
118732
|
studentId,
|
|
@@ -118630,20 +118749,21 @@ class TimebackAssessmentRuntimeService {
|
|
|
118630
118749
|
this.listAttempts(attemptScope)
|
|
118631
118750
|
]);
|
|
118632
118751
|
const attempts = listing.attempts;
|
|
118633
|
-
const
|
|
118634
|
-
|
|
118635
|
-
|
|
118636
|
-
return
|
|
118752
|
+
const initialResolution = await this.resumeAvailableAttempt(tests, attempts, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeSnapshot(resumeDecision, attempts, context2.integration));
|
|
118753
|
+
if (initialResolution.kind === "resumed") {
|
|
118754
|
+
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118755
|
+
return initialResolution.snapshot;
|
|
118637
118756
|
}
|
|
118757
|
+
let decision = this.requireDecision(initialResolution.decision, input.purpose);
|
|
118638
118758
|
let assessment = await this.loadAssessment(decision.test.qtiTestIdentifier);
|
|
118639
118759
|
const refreshedListing = await this.listAttempts(attemptScope);
|
|
118640
118760
|
const refreshed = refreshedListing.attempts;
|
|
118641
|
-
const
|
|
118642
|
-
|
|
118643
|
-
|
|
118644
|
-
return
|
|
118761
|
+
const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeSnapshot(resumeDecision, refreshed, context2.integration));
|
|
118762
|
+
if (refreshedResolution.kind === "resumed") {
|
|
118763
|
+
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118764
|
+
return refreshedResolution.snapshot;
|
|
118645
118765
|
}
|
|
118646
|
-
decision =
|
|
118766
|
+
decision = this.requireDecision(refreshedResolution.decision, input.purpose);
|
|
118647
118767
|
if (assessment.identifier !== decision.test.qtiTestIdentifier) {
|
|
118648
118768
|
assessment = await this.loadAssessment(decision.test.qtiTestIdentifier);
|
|
118649
118769
|
}
|
|
@@ -118701,6 +118821,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118701
118821
|
grade: context2.integration.grade
|
|
118702
118822
|
})
|
|
118703
118823
|
});
|
|
118824
|
+
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118704
118825
|
return this.snapshot(result, metadata2, assessment, context2.integration);
|
|
118705
118826
|
});
|
|
118706
118827
|
}
|
|
@@ -118710,6 +118831,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118710
118831
|
input
|
|
118711
118832
|
}) {
|
|
118712
118833
|
const requestFingerprint = this.startRequestFingerprint(input);
|
|
118834
|
+
const pendingSupersessions = new Set;
|
|
118713
118835
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
118714
118836
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
118715
118837
|
studentId,
|
|
@@ -118731,22 +118853,23 @@ class TimebackAssessmentRuntimeService {
|
|
|
118731
118853
|
this.liveTests(context2.integration.id, input.purpose, db2, undefined, input.diagnosticKey),
|
|
118732
118854
|
this.listAttempts(attemptScope)
|
|
118733
118855
|
]);
|
|
118734
|
-
const
|
|
118735
|
-
|
|
118736
|
-
|
|
118737
|
-
return
|
|
118856
|
+
const initialResolution = await this.resumeAvailableAttempt(tests, listing.attempts, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeDiagnosticSnapshot(resumeDecision, listing.attempts, context2.integration, db2));
|
|
118857
|
+
if (initialResolution.kind === "resumed") {
|
|
118858
|
+
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118859
|
+
return initialResolution.snapshot;
|
|
118738
118860
|
}
|
|
118861
|
+
let decision = this.requireDecision(initialResolution.decision, input.purpose);
|
|
118739
118862
|
let definition = await this.requireDiagnosticDefinition(decision.test.id, context2.integration.id, db2, true);
|
|
118740
118863
|
let assessment = await this.loadAssessment(definition.qtiTestIdentifier);
|
|
118741
118864
|
let routing = await this.initializeHostedDiagnostic(definition, assessment);
|
|
118742
118865
|
const refreshedListing = await this.listAttempts(attemptScope);
|
|
118743
118866
|
const refreshed = refreshedListing.attempts;
|
|
118744
|
-
const
|
|
118745
|
-
|
|
118746
|
-
|
|
118747
|
-
return
|
|
118867
|
+
const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeDiagnosticSnapshot(resumeDecision, refreshed, context2.integration, db2));
|
|
118868
|
+
if (refreshedResolution.kind === "resumed") {
|
|
118869
|
+
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118870
|
+
return refreshedResolution.snapshot;
|
|
118748
118871
|
}
|
|
118749
|
-
decision =
|
|
118872
|
+
decision = this.requireDecision(refreshedResolution.decision, input.purpose);
|
|
118750
118873
|
if (definition.id !== decision.test.id) {
|
|
118751
118874
|
definition = await this.requireDiagnosticDefinition(decision.test.id, context2.integration.id, db2, true);
|
|
118752
118875
|
assessment = await this.loadAssessment(definition.qtiTestIdentifier);
|
|
@@ -118815,6 +118938,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118815
118938
|
grade: context2.integration.grade
|
|
118816
118939
|
})
|
|
118817
118940
|
});
|
|
118941
|
+
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118818
118942
|
return this.diagnosticSnapshot(result, metadata2, assessment, context2.integration, routing.state);
|
|
118819
118943
|
});
|
|
118820
118944
|
}
|
|
@@ -118823,7 +118947,9 @@ class TimebackAssessmentRuntimeService {
|
|
|
118823
118947
|
studentId,
|
|
118824
118948
|
input
|
|
118825
118949
|
}) {
|
|
118950
|
+
const preparationStartedAt = Date.now();
|
|
118826
118951
|
const requestFingerprint = reviewRequestFingerprint(input, DEFAULT_REVIEW_SELECTION_POLICY);
|
|
118952
|
+
const pendingSupersessions = new Set;
|
|
118827
118953
|
const normalizedRequest = normalizeReviewRequest(input);
|
|
118828
118954
|
const candidates = await this.candidateIntegrations(gameId, studentId, input);
|
|
118829
118955
|
const lockKeys = candidates.rows.map((row) => assessmentRuntimeStartLockKey({
|
|
@@ -118847,38 +118973,44 @@ class TimebackAssessmentRuntimeService {
|
|
|
118847
118973
|
this.listAttempts(attemptScope)
|
|
118848
118974
|
]);
|
|
118849
118975
|
const attempts = listing.attempts;
|
|
118850
|
-
const
|
|
118851
|
-
|
|
118852
|
-
|
|
118853
|
-
return
|
|
118976
|
+
const initialResolution = await this.resumeAvailableAttempt(tests, attempts, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, attempts, listing.reviewChildren, context2));
|
|
118977
|
+
if (initialResolution.kind === "resumed") {
|
|
118978
|
+
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118979
|
+
return initialResolution.snapshot;
|
|
118854
118980
|
}
|
|
118981
|
+
let decision = initialResolution.decision;
|
|
118855
118982
|
if (tests.length === 0 || !decision) {
|
|
118856
118983
|
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(input.purpose));
|
|
118857
118984
|
}
|
|
118858
118985
|
if (tests.length > 1) {
|
|
118859
118986
|
throw new ValidationError("Standards review requires exactly one live review-bank assessment.");
|
|
118860
118987
|
}
|
|
118861
|
-
|
|
118988
|
+
const manifestStartedAt = Date.now();
|
|
118989
|
+
let catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
|
|
118990
|
+
this.recordReviewPreparationPhase("manifest_load", manifestStartedAt, {
|
|
118991
|
+
candidateCount: catalog.bank.items.length
|
|
118992
|
+
});
|
|
118862
118993
|
const refreshedListing = await this.listAttempts(attemptScope);
|
|
118863
118994
|
const refreshed = refreshedListing.attempts;
|
|
118864
|
-
const
|
|
118865
|
-
|
|
118866
|
-
|
|
118867
|
-
return
|
|
118995
|
+
const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, refreshed, refreshedListing.reviewChildren, context2));
|
|
118996
|
+
if (refreshedResolution.kind === "resumed") {
|
|
118997
|
+
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118998
|
+
return refreshedResolution.snapshot;
|
|
118868
118999
|
}
|
|
119000
|
+
const refreshedDecision = refreshedResolution.decision;
|
|
118869
119001
|
if (!refreshedDecision || refreshedDecision.kind !== "start") {
|
|
118870
119002
|
throw AssessmentRuntimeError.from(assessmentNoEligibleTests(input.purpose));
|
|
118871
119003
|
}
|
|
118872
119004
|
decision = refreshedDecision;
|
|
118873
|
-
if (
|
|
118874
|
-
|
|
119005
|
+
if (catalog.test.identifier !== decision.test.qtiTestIdentifier) {
|
|
119006
|
+
catalog = await this.loadReviewBankCatalog(decision.test.qtiTestIdentifier);
|
|
118875
119007
|
}
|
|
118876
119008
|
const exposures = this.reviewExposures(refreshedListing.reviewChildren.values(), {
|
|
118877
|
-
bankRevision:
|
|
119009
|
+
bankRevision: catalog.bank.bankRevision
|
|
118878
119010
|
});
|
|
118879
119011
|
const selected = selectReviewItems({
|
|
118880
119012
|
request: input,
|
|
118881
|
-
bank:
|
|
119013
|
+
bank: catalog.bank,
|
|
118882
119014
|
exposures
|
|
118883
119015
|
});
|
|
118884
119016
|
if (selected.selections.length === 0) {
|
|
@@ -118887,7 +119019,13 @@ class TimebackAssessmentRuntimeService {
|
|
|
118887
119019
|
}
|
|
118888
119020
|
throw AssessmentRuntimeError.from(assessmentReviewBankShortage(selected.fulfillment));
|
|
118889
119021
|
}
|
|
119022
|
+
const hydrationStartedAt = Date.now();
|
|
119023
|
+
const source = await this.hydrateReviewBankSelection(catalog, selected.selections.map((selection) => selection.itemIdentifier));
|
|
119024
|
+
this.recordReviewPreparationPhase("selected_hydration", hydrationStartedAt, {
|
|
119025
|
+
selectedCount: selected.selections.length
|
|
119026
|
+
});
|
|
118890
119027
|
const assessment = projectReviewAssessment(source.assessment, source.bank, selected.selections);
|
|
119028
|
+
const provisioningStartedAt = Date.now();
|
|
118891
119029
|
const parentLineItemId = await this.ensureReviewBankLineItem({
|
|
118892
119030
|
integration: context2.integration,
|
|
118893
119031
|
activityId: input.activityId,
|
|
@@ -118950,6 +119088,12 @@ class TimebackAssessmentRuntimeService {
|
|
|
118950
119088
|
existingChildResults: refreshedListing.reviewChildren,
|
|
118951
119089
|
lookupMissingResults: false
|
|
118952
119090
|
});
|
|
119091
|
+
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
119092
|
+
this.recordReviewPreparationPhase("provisioning", provisioningStartedAt);
|
|
119093
|
+
this.recordReviewPreparationPhase("total", preparationStartedAt, {
|
|
119094
|
+
candidateCount: catalog.bank.items.length,
|
|
119095
|
+
selectedCount: selected.selections.length
|
|
119096
|
+
});
|
|
118953
119097
|
return this.snapshot(result, metadata2, assessment, context2.integration);
|
|
118954
119098
|
});
|
|
118955
119099
|
}
|
|
@@ -119303,7 +119447,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
119303
119447
|
preparationAttempts += 1;
|
|
119304
119448
|
preview = await this.scoreDiagnosticItemSubmission(params, input);
|
|
119305
119449
|
} else {
|
|
119306
|
-
await this.projectDiagnosticItemResponse(committed.projection);
|
|
119450
|
+
await this.projectDiagnosticItemResponse(committed.projection, preview?.assessment);
|
|
119307
119451
|
return committed.response;
|
|
119308
119452
|
}
|
|
119309
119453
|
}
|
|
@@ -119717,7 +119861,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
119717
119861
|
where: and(eq(gameTimebackAssessmentTests.id, test.id), eq(gameTimebackAssessmentTests.integrationId, integration.id))
|
|
119718
119862
|
}) : undefined;
|
|
119719
119863
|
const diagnostic = definition?.diagnosticKey ? await this.initializeHostedDiagnostic(definition, loaded.assessment) : null;
|
|
119720
|
-
const reviewBank = purpose === "review" ? await buildReviewBankIndex(loaded.assessment, loaded.questions) : null;
|
|
119864
|
+
const reviewBank = purpose === "review" ? await buildReviewBankIndex(loaded.assessment, loaded.questions, { customOnly: true }) : null;
|
|
119721
119865
|
return {
|
|
119722
119866
|
...test,
|
|
119723
119867
|
live: true,
|
|
@@ -119780,7 +119924,13 @@ class TimebackAssessmentRuntimeService {
|
|
|
119780
119924
|
const enrollmentByCourse = new Map(enrollments.map((enrollment) => [enrollment.course.id, enrollment]));
|
|
119781
119925
|
const candidateRows = integrations.filter((integration) => enrollmentByCourse.has(integration.courseId));
|
|
119782
119926
|
const liveTestRows = candidateRows.length === 0 ? [] : await db2.query.gameTimebackAssessmentTests.findMany({
|
|
119783
|
-
where: and(inArray(gameTimebackAssessmentTests.integrationId, candidateRows.map((row) => row.id)), eq(gameTimebackAssessmentTests.purpose, input.purpose), eq(gameTimebackAssessmentTests.status, "live"))
|
|
119927
|
+
where: and(inArray(gameTimebackAssessmentTests.integrationId, candidateRows.map((row) => row.id)), eq(gameTimebackAssessmentTests.purpose, input.purpose), eq(gameTimebackAssessmentTests.status, "live")),
|
|
119928
|
+
columns: {
|
|
119929
|
+
integrationId: true,
|
|
119930
|
+
standardFramework: true,
|
|
119931
|
+
standardIdentifier: true,
|
|
119932
|
+
diagnosticKey: true
|
|
119933
|
+
}
|
|
119784
119934
|
});
|
|
119785
119935
|
const matchesMasteryStandard = input.purpose === "mastery" ? masteryStandardMatcher(input.standard) : undefined;
|
|
119786
119936
|
const integrationIdsWithLiveTests = new Set(liveTestRows.filter((row) => (!matchesMasteryStandard || matchesMasteryStandard(assessmentStandardForRow(row))) && (input.purpose !== "diagnostic" || row.diagnosticKey === input.diagnosticKey)).map((row) => row.integrationId));
|
|
@@ -119973,6 +120123,75 @@ class TimebackAssessmentRuntimeService {
|
|
|
119973
120123
|
});
|
|
119974
120124
|
return this.diagnosticSnapshot(resumed.result, resumed.metadata, assessment, integration, routing.state);
|
|
119975
120125
|
}
|
|
120126
|
+
async resumeAvailableAttempt(tests, attempts, requestFingerprint, pendingSupersessions, resume) {
|
|
120127
|
+
const select3 = () => selectRuntimeAssessment(tests, [...this.attemptsMatchingRequest(attempts, requestFingerprint).values()].map((entry2) => entry2.selection));
|
|
120128
|
+
return resolveAvailableAssessmentAttempt(select3(), select3, (decision) => this.resumeSnapshotOrStageSupersession(decision, attempts, pendingSupersessions, () => resume(decision)));
|
|
120129
|
+
}
|
|
120130
|
+
async resumeSnapshotOrStageSupersession(decision, attempts, pendingSupersessions, resume) {
|
|
120131
|
+
try {
|
|
120132
|
+
return await resume();
|
|
120133
|
+
} catch (error88) {
|
|
120134
|
+
if (!(error88 instanceof AssessmentRuntimeError) || error88.runtimeCode !== "SELECTED_TEST_VERSION_UNAVAILABLE") {
|
|
120135
|
+
throw error88;
|
|
120136
|
+
}
|
|
120137
|
+
if (shouldConfirmAssessmentVersionUnavailable(error88.details)) {
|
|
120138
|
+
await sleep(TimebackAssessmentRuntimeService.ASSESSMENT_VERSION_CONFIRMATION_DELAY_MS);
|
|
120139
|
+
try {
|
|
120140
|
+
return await resume();
|
|
120141
|
+
} catch (confirmationError) {
|
|
120142
|
+
if (!(confirmationError instanceof AssessmentRuntimeError) || confirmationError.runtimeCode !== "SELECTED_TEST_VERSION_UNAVAILABLE") {
|
|
120143
|
+
throw confirmationError;
|
|
120144
|
+
}
|
|
120145
|
+
}
|
|
120146
|
+
}
|
|
120147
|
+
const staged = attempts.get(decision.attempt.attemptId);
|
|
120148
|
+
pendingSupersessions.add(staged.result.sourcedId);
|
|
120149
|
+
stageAssessmentAttemptSupersession(staged);
|
|
120150
|
+
addEvent("assessment.attempt_supersession_staged", {
|
|
120151
|
+
"app.assessment.attempt_id": decision.attempt.attemptId,
|
|
120152
|
+
"app.assessment.reason": "selected_test_version_unavailable"
|
|
120153
|
+
});
|
|
120154
|
+
return null;
|
|
120155
|
+
}
|
|
120156
|
+
}
|
|
120157
|
+
async persistPendingSupersessions(attemptIds) {
|
|
120158
|
+
for (const attemptId of [...attemptIds].toSorted()) {
|
|
120159
|
+
await this.deps.assessmentRuntimeLock(this.deps.db, assessmentRuntimeAttemptLockKey(attemptId), async () => {
|
|
120160
|
+
let result;
|
|
120161
|
+
try {
|
|
120162
|
+
result = await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
120163
|
+
} catch (error88) {
|
|
120164
|
+
if (isApiError(error88) && error88.statusCode === 404) {
|
|
120165
|
+
return;
|
|
120166
|
+
}
|
|
120167
|
+
throw error88;
|
|
120168
|
+
}
|
|
120169
|
+
const update2 = buildAssessmentAttemptSupersessionUpdate(result);
|
|
120170
|
+
if (!update2) {
|
|
120171
|
+
return;
|
|
120172
|
+
}
|
|
120173
|
+
await this.requireClient().api.oneroster.assessmentResults.upsert(attemptId, update2);
|
|
120174
|
+
addEvent("assessment.attempt_superseded", {
|
|
120175
|
+
"app.assessment.attempt_id": attemptId,
|
|
120176
|
+
"app.assessment.reason": "selected_test_version_unavailable"
|
|
120177
|
+
});
|
|
120178
|
+
});
|
|
120179
|
+
}
|
|
120180
|
+
}
|
|
120181
|
+
async persistPendingSupersessionsBestEffort(attemptIds) {
|
|
120182
|
+
if (attemptIds.size === 0) {
|
|
120183
|
+
return;
|
|
120184
|
+
}
|
|
120185
|
+
try {
|
|
120186
|
+
await this.persistPendingSupersessions(attemptIds);
|
|
120187
|
+
} catch (error88) {
|
|
120188
|
+
addEvent("assessment.attempt_supersession_persist_failed", {
|
|
120189
|
+
"app.assessment.attempt_ids": [...attemptIds].toSorted().join(","),
|
|
120190
|
+
"exception.type": errorType(error88),
|
|
120191
|
+
"app.error.message": errorMessage2(error88)
|
|
120192
|
+
});
|
|
120193
|
+
}
|
|
120194
|
+
}
|
|
119976
120195
|
async resumeReviewSnapshot(decision, attempts, existingChildResults, context2) {
|
|
119977
120196
|
if (decision.additionalResumableAttemptIds.length > 0) {
|
|
119978
120197
|
addEvent("assessment.multiple_resumable_attempts", {
|
|
@@ -119984,7 +120203,8 @@ class TimebackAssessmentRuntimeService {
|
|
|
119984
120203
|
if (resumed.metadata.purpose !== "review") {
|
|
119985
120204
|
throw new Error("A standards-review request selected a non-review attempt");
|
|
119986
120205
|
}
|
|
119987
|
-
const
|
|
120206
|
+
const catalog = await this.loadReviewBankCatalog(resumed.metadata.selectedTest.identifier, resumed.metadata.selectedTest.contentRevision);
|
|
120207
|
+
const currentSource = await this.hydrateReviewBankSelection(catalog, resumed.metadata.review.selections.map((selection) => selection.itemIdentifier));
|
|
119988
120208
|
const source = this.pinnedReviewBankSource(currentSource, resumed.metadata);
|
|
119989
120209
|
const assessment = projectReviewAssessment(source.assessment, source.bank, resumed.metadata.review.selections);
|
|
119990
120210
|
await this.ensureReviewChildResults({
|
|
@@ -119998,20 +120218,68 @@ class TimebackAssessmentRuntimeService {
|
|
|
119998
120218
|
});
|
|
119999
120219
|
return this.snapshot(resumed.result, resumed.metadata, assessment, context2.integration);
|
|
120000
120220
|
}
|
|
120001
|
-
|
|
120221
|
+
recordReviewPreparationPhase(phase, startedAt, counts = {}) {
|
|
120222
|
+
addEvent("assessment.review_preparation_phase", {
|
|
120223
|
+
"app.assessment.review_phase": phase,
|
|
120224
|
+
"app.assessment.duration_ms": Date.now() - startedAt,
|
|
120225
|
+
...counts.candidateCount === undefined ? {} : { "app.assessment.candidate_count": counts.candidateCount },
|
|
120226
|
+
...counts.selectedCount === undefined ? {} : { "app.assessment.selected_count": counts.selectedCount }
|
|
120227
|
+
});
|
|
120228
|
+
}
|
|
120229
|
+
async loadReviewBankCatalog(identifier, expectedSourceRevision) {
|
|
120002
120230
|
if (expectedSourceRevision) {
|
|
120003
120231
|
const cached3 = this.reviewBankCache.get(`${identifier}\x00${expectedSourceRevision}`);
|
|
120004
120232
|
if (cached3) {
|
|
120005
120233
|
return cached3;
|
|
120006
120234
|
}
|
|
120007
120235
|
}
|
|
120008
|
-
|
|
120009
|
-
|
|
120010
|
-
|
|
120011
|
-
|
|
120012
|
-
|
|
120013
|
-
|
|
120014
|
-
|
|
120236
|
+
let loaded;
|
|
120237
|
+
try {
|
|
120238
|
+
loaded = await loadQtiTestReferences(this.requireClient(), identifier);
|
|
120239
|
+
} catch (error88) {
|
|
120240
|
+
if (expectedSourceRevision && isApiError(error88) && error88.statusCode === 404) {
|
|
120241
|
+
throw new AssessmentRuntimeError("SELECTED_TEST_VERSION_UNAVAILABLE", `The selected assessment version for ${identifier} is no longer available.`, { identifier, expectedRevision: expectedSourceRevision });
|
|
120242
|
+
}
|
|
120243
|
+
throw error88;
|
|
120244
|
+
}
|
|
120245
|
+
let bank;
|
|
120246
|
+
try {
|
|
120247
|
+
bank = await reviewBankIndexFromManifest(loaded.test.metadata, {
|
|
120248
|
+
bankIdentifier: identifier,
|
|
120249
|
+
membershipItemIdentifiers: loaded.references.questions.map((question) => question.reference.identifier)
|
|
120250
|
+
});
|
|
120251
|
+
} catch (error88) {
|
|
120252
|
+
throw new ServiceUnavailableError(`Review bank ${identifier} has stale or invalid routing metadata. Use Update Mapping in assessment authoring.`, { reason: errorMessage2(error88) });
|
|
120253
|
+
}
|
|
120254
|
+
if (!bank) {
|
|
120255
|
+
throw new ServiceUnavailableError(`Review bank ${identifier} needs its authoring mapping updated before it can serve review questions.`);
|
|
120256
|
+
}
|
|
120257
|
+
if (expectedSourceRevision && bank.sourceContentRevision !== expectedSourceRevision) {
|
|
120258
|
+
throw new AssessmentRuntimeError("SELECTED_TEST_VERSION_UNAVAILABLE", `The selected assessment version for ${identifier} is no longer available.`, {
|
|
120259
|
+
identifier,
|
|
120260
|
+
expectedRevision: expectedSourceRevision,
|
|
120261
|
+
currentRevision: bank.sourceContentRevision
|
|
120262
|
+
});
|
|
120263
|
+
}
|
|
120264
|
+
const catalog = { ...loaded, bank };
|
|
120265
|
+
this.reviewBankCache.set(`${identifier}\x00${bank.sourceContentRevision}`, catalog);
|
|
120266
|
+
return catalog;
|
|
120267
|
+
}
|
|
120268
|
+
async hydrateReviewBankSelection(catalog, itemIdentifiers) {
|
|
120269
|
+
const cacheKey2 = [
|
|
120270
|
+
catalog.test.identifier,
|
|
120271
|
+
catalog.bank.sourceContentRevision,
|
|
120272
|
+
...itemIdentifiers
|
|
120273
|
+
].join("\x00");
|
|
120274
|
+
const cached3 = this.reviewSelectionCache.get(cacheKey2);
|
|
120275
|
+
if (cached3) {
|
|
120276
|
+
return { assessment: cached3, bank: catalog.bank };
|
|
120277
|
+
}
|
|
120278
|
+
const questions = await hydrateQtiTestQuestionSelection(this.requireClient(), catalog.references, itemIdentifiers);
|
|
120279
|
+
const assessment = await buildPlayableAssessment(catalog.test, questions);
|
|
120280
|
+
assessment.contentRevision = catalog.bank.sourceContentRevision;
|
|
120281
|
+
this.reviewSelectionCache.set(cacheKey2, assessment);
|
|
120282
|
+
return { assessment, bank: catalog.bank };
|
|
120015
120283
|
}
|
|
120016
120284
|
pinnedReviewBankSource(source, metadata2) {
|
|
120017
120285
|
return {
|
|
@@ -120042,7 +120310,8 @@ class TimebackAssessmentRuntimeService {
|
|
|
120042
120310
|
if (metadata2.purpose !== "review") {
|
|
120043
120311
|
return this.loadAssessment(metadata2.selectedTest.identifier, metadata2.selectedTest.contentRevision);
|
|
120044
120312
|
}
|
|
120045
|
-
const
|
|
120313
|
+
const catalog = await this.loadReviewBankCatalog(metadata2.selectedTest.identifier, metadata2.selectedTest.contentRevision);
|
|
120314
|
+
const currentSource = await this.hydrateReviewBankSelection(catalog, metadata2.review.selections.map((selection) => selection.itemIdentifier));
|
|
120046
120315
|
const source = this.pinnedReviewBankSource(currentSource, metadata2);
|
|
120047
120316
|
return projectReviewAssessment(source.assessment, source.bank, metadata2.review.selections);
|
|
120048
120317
|
}
|
|
@@ -120665,6 +120934,14 @@ class TimebackAssessmentRuntimeService {
|
|
|
120665
120934
|
return AssessmentRuntimeError.from(assessmentAttemptUnauthorized(attemptId));
|
|
120666
120935
|
}
|
|
120667
120936
|
async snapshotForResult(result, metadata2, integration) {
|
|
120937
|
+
if (isAssessmentAttemptSuperseded({
|
|
120938
|
+
inProgress: result.inProgress ?? "",
|
|
120939
|
+
scoreStatus: result.scoreStatus
|
|
120940
|
+
})) {
|
|
120941
|
+
throw new GoneError("This assessment attempt has been superseded.", {
|
|
120942
|
+
attemptId: result.sourcedId
|
|
120943
|
+
});
|
|
120944
|
+
}
|
|
120668
120945
|
const assessment = await this.loadAttemptAssessment(metadata2);
|
|
120669
120946
|
if (this.isPlatformRoutedDiagnosticMetadata(metadata2)) {
|
|
120670
120947
|
const routing = await this.loadAttemptDiagnosticRouting(metadata2, assessment);
|
|
@@ -122010,6 +122287,60 @@ function buildQtiLibraryListPlan(params) {
|
|
|
122010
122287
|
}
|
|
122011
122288
|
var PLAYCADEMY_QTI_SOURCE_PREFIX = "playcademy-test-";
|
|
122012
122289
|
var PLAYCADEMY_QTI_SOURCE_UPPER_BOUND = "playcademy-test.";
|
|
122290
|
+
function reviewMappingIssueSummary(label, identifiers) {
|
|
122291
|
+
if (identifiers.length === 0) {
|
|
122292
|
+
return null;
|
|
122293
|
+
}
|
|
122294
|
+
const displayed = identifiers.slice(0, 10).join(", ");
|
|
122295
|
+
const remainder = identifiers.length > 10 ? `, and ${identifiers.length - 10} more` : "";
|
|
122296
|
+
return `${label} (${identifiers.length}): ${displayed}${remainder}`;
|
|
122297
|
+
}
|
|
122298
|
+
async function prepareReviewMappingUpdate(test, questions) {
|
|
122299
|
+
const contentRevision = await assessmentContentRevision(test, questions);
|
|
122300
|
+
const assessment = {
|
|
122301
|
+
identifier: test.identifier,
|
|
122302
|
+
contractVersion: 1,
|
|
122303
|
+
contentRevision,
|
|
122304
|
+
title: test.title,
|
|
122305
|
+
items: questions.questions.map(({ question }) => ({
|
|
122306
|
+
identifier: question.identifier,
|
|
122307
|
+
title: question.title,
|
|
122308
|
+
prompt: "",
|
|
122309
|
+
maxScore: 1,
|
|
122310
|
+
interactions: []
|
|
122311
|
+
}))
|
|
122312
|
+
};
|
|
122313
|
+
const bank = await buildReviewBankIndex(assessment, questions, { customOnly: true });
|
|
122314
|
+
const issues = reviewBankMappingIssues(bank);
|
|
122315
|
+
if (issues.missingOrInvalidItemIdentifiers.length > 0 || issues.multipleStandardItemIdentifiers.length > 0) {
|
|
122316
|
+
const details = [
|
|
122317
|
+
reviewMappingIssueSummary("missing or invalid associations", issues.missingOrInvalidItemIdentifiers),
|
|
122318
|
+
reviewMappingIssueSummary("multiple associations", issues.multipleStandardItemIdentifiers)
|
|
122319
|
+
].filter((detail) => detail !== null);
|
|
122320
|
+
throw new ValidationError(`Every review question must have exactly one valid standard/node association. ${details.join("; ")}.`);
|
|
122321
|
+
}
|
|
122322
|
+
const manifest = await buildReviewBankManifest(bank);
|
|
122323
|
+
return {
|
|
122324
|
+
update: {
|
|
122325
|
+
...buildQtiTestUpdateInput(test, test.title),
|
|
122326
|
+
metadata: {
|
|
122327
|
+
...test.metadata,
|
|
122328
|
+
[PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY]: manifest
|
|
122329
|
+
}
|
|
122330
|
+
},
|
|
122331
|
+
summary: {
|
|
122332
|
+
itemCount: bank.items.length,
|
|
122333
|
+
standardCount: Object.keys(manifest.itemsByStandard).length,
|
|
122334
|
+
sourceFingerprint: manifest.sourceFingerprint
|
|
122335
|
+
}
|
|
122336
|
+
};
|
|
122337
|
+
}
|
|
122338
|
+
var init_timeback_review_mapping_util = __esm(() => {
|
|
122339
|
+
init_assessment_runtime2();
|
|
122340
|
+
init_errors2();
|
|
122341
|
+
init_timeback_assessment_runtime_util();
|
|
122342
|
+
init_timeback_qti_authoring_util();
|
|
122343
|
+
});
|
|
122013
122344
|
function databaseConstraintName(error88) {
|
|
122014
122345
|
if (typeof error88 !== "object" || error88 === null) {
|
|
122015
122346
|
return;
|
|
@@ -122091,7 +122422,8 @@ class TimebackAssessmentsService {
|
|
|
122091
122422
|
ownerGameSlug: ownership.gameSlug,
|
|
122092
122423
|
integrationId,
|
|
122093
122424
|
subject: integration.subject,
|
|
122094
|
-
grade: String(integration.grade)
|
|
122425
|
+
grade: String(integration.grade),
|
|
122426
|
+
[PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY]: emptyReviewBankManifest(input.qtiTestIdentifier)
|
|
122095
122427
|
}
|
|
122096
122428
|
});
|
|
122097
122429
|
try {
|
|
@@ -122160,7 +122492,9 @@ class TimebackAssessmentsService {
|
|
|
122160
122492
|
assertAssessmentHasQuestions(loaded.questions.questions);
|
|
122161
122493
|
assertPlayableAssessmentImportQuestions(manifest.targetStatus, loaded.questions.questions.map(({ question }) => question));
|
|
122162
122494
|
if (entry2.purpose === "review") {
|
|
122163
|
-
assertReviewAssessmentHasStandards(loaded.questions.questions.map(({ question }) =>
|
|
122495
|
+
assertReviewAssessmentHasStandards(loaded.questions.questions.map(({ question }) => reviewRoutingStandardRefsFromMetadata(question.metadata, {
|
|
122496
|
+
customOnly: true
|
|
122497
|
+
}).length));
|
|
122164
122498
|
}
|
|
122165
122499
|
return { entry: entry2, test: loaded.test };
|
|
122166
122500
|
} catch (error88) {
|
|
@@ -122312,6 +122646,10 @@ class TimebackAssessmentsService {
|
|
|
122312
122646
|
if (input.status !== undefined) {
|
|
122313
122647
|
validateAssessmentStatusTransition(row.status, input.status);
|
|
122314
122648
|
}
|
|
122649
|
+
if (!publishing && !activatingReview && nextPurpose === "review" && row.purpose !== "review") {
|
|
122650
|
+
const ownership = await this.requireQtiTestOwnershipContext(integrationId, tx);
|
|
122651
|
+
await this.rebuildReviewMapping(this.requireClient(), qtiTestIdentifier, ownership.gameSlug);
|
|
122652
|
+
}
|
|
122315
122653
|
if (input.title !== undefined) {
|
|
122316
122654
|
assertDraftAssessment(row);
|
|
122317
122655
|
assertAllAssessmentAssociationsDraft(associations);
|
|
@@ -122330,7 +122668,8 @@ class TimebackAssessmentsService {
|
|
|
122330
122668
|
updates.diagnosticKey = nextDiagnostic.diagnosticKey;
|
|
122331
122669
|
updates.diagnosticRoutingManifest = nextDiagnostic.routingManifest;
|
|
122332
122670
|
} else {
|
|
122333
|
-
|
|
122671
|
+
const reviewOwnership = nextPurpose === "review" && nextStatus === "live" ? await this.requireQtiTestOwnershipContext(integrationId, tx) : undefined;
|
|
122672
|
+
await this.validateAssessmentHasQuestions(qtiTestIdentifier, reviewOwnership?.gameSlug);
|
|
122334
122673
|
}
|
|
122335
122674
|
}
|
|
122336
122675
|
let updated = row;
|
|
@@ -122425,6 +122764,19 @@ class TimebackAssessmentsService {
|
|
|
122425
122764
|
});
|
|
122426
122765
|
return { ...result, questions };
|
|
122427
122766
|
}
|
|
122767
|
+
async updateReviewMapping(integrationId, qtiTestIdentifier) {
|
|
122768
|
+
return this.withLockedAssessmentAssociations(integrationId, qtiTestIdentifier, async (row, tx, associations) => {
|
|
122769
|
+
if (!associations.some((association) => association.purpose === "review")) {
|
|
122770
|
+
throw new ValidationError("Review mapping is available only for standards-review assessments.");
|
|
122771
|
+
}
|
|
122772
|
+
const client2 = this.requireClient();
|
|
122773
|
+
const ownership = await this.requireQtiTestOwnershipContext(integrationId, tx);
|
|
122774
|
+
const result = await this.rebuildReviewMapping(client2, qtiTestIdentifier, ownership.gameSlug);
|
|
122775
|
+
setAttribute("app.assessment.operation", "update_review_mapping");
|
|
122776
|
+
setAttribute("app.assessment.review_mapping_item_count", result.itemCount);
|
|
122777
|
+
return result;
|
|
122778
|
+
});
|
|
122779
|
+
}
|
|
122428
122780
|
async listQuestionLibrary(integrationId, params) {
|
|
122429
122781
|
const client2 = this.requireClient();
|
|
122430
122782
|
await this.requireIntegration(integrationId);
|
|
@@ -122465,7 +122817,8 @@ class TimebackAssessmentsService {
|
|
|
122465
122817
|
integrationId,
|
|
122466
122818
|
subject: integration.subject,
|
|
122467
122819
|
grade: String(integration.grade),
|
|
122468
|
-
copiedFromTestIdentifier: sourceTestIdentifier
|
|
122820
|
+
copiedFromTestIdentifier: sourceTestIdentifier,
|
|
122821
|
+
[PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY]: emptyReviewBankManifest(targetTestIdentifier)
|
|
122469
122822
|
}, itemPlan);
|
|
122470
122823
|
const attemptedItemIdentifiers = [];
|
|
122471
122824
|
let testCreationAttempted = false;
|
|
@@ -122853,16 +123206,20 @@ class TimebackAssessmentsService {
|
|
|
122853
123206
|
const plan = buildQtiLibraryListPlan(params);
|
|
122854
123207
|
return list(plan.params);
|
|
122855
123208
|
}
|
|
122856
|
-
async validateAssessmentHasQuestions(qtiTestIdentifier,
|
|
123209
|
+
async validateAssessmentHasQuestions(qtiTestIdentifier, reviewGameSlug) {
|
|
122857
123210
|
const client2 = this.requireClient();
|
|
122858
|
-
const result = await
|
|
123211
|
+
const [test, result] = await Promise.all([
|
|
123212
|
+
client2.qtiApi.assessmentTests.get(qtiTestIdentifier),
|
|
123213
|
+
client2.qtiApi.assessmentTests.getQuestions(qtiTestIdentifier)
|
|
123214
|
+
]);
|
|
122859
123215
|
assertAssessmentHasQuestions(result.questions);
|
|
122860
123216
|
const hydrated = await hydrateQtiTestQuestions(client2, result);
|
|
122861
123217
|
for (const { question } of hydrated.questions) {
|
|
122862
123218
|
assertPlayableQtiQuestion(question);
|
|
122863
123219
|
}
|
|
122864
|
-
if (
|
|
122865
|
-
|
|
123220
|
+
if (reviewGameSlug) {
|
|
123221
|
+
assertQtiTestOwnedByGame(test, reviewGameSlug);
|
|
123222
|
+
await this.writeReviewMapping(client2, test, hydrated);
|
|
122866
123223
|
}
|
|
122867
123224
|
}
|
|
122868
123225
|
async prepareDiagnosticAssociationChanges(row, nextPurpose, requested) {
|
|
@@ -122927,6 +123284,19 @@ class TimebackAssessmentsService {
|
|
|
122927
123284
|
routingManifest: validation.manifest
|
|
122928
123285
|
};
|
|
122929
123286
|
}
|
|
123287
|
+
async rebuildReviewMapping(client2, qtiTestIdentifier, gameSlug) {
|
|
123288
|
+
const [test, references] = await Promise.all([
|
|
123289
|
+
client2.qtiApi.assessmentTests.get(qtiTestIdentifier),
|
|
123290
|
+
client2.qtiApi.assessmentTests.getQuestions(qtiTestIdentifier)
|
|
123291
|
+
]);
|
|
123292
|
+
assertQtiTestOwnedByGame(test, gameSlug);
|
|
123293
|
+
return this.writeReviewMapping(client2, test, await hydrateQtiTestQuestions(client2, references));
|
|
123294
|
+
}
|
|
123295
|
+
async writeReviewMapping(client2, test, questions) {
|
|
123296
|
+
const prepared = await prepareReviewMappingUpdate(test, questions);
|
|
123297
|
+
await client2.qtiApi.assessmentTests.update(test.identifier, prepared.update);
|
|
123298
|
+
return prepared.summary;
|
|
123299
|
+
}
|
|
122930
123300
|
}
|
|
122931
123301
|
var init_timeback_assessments_service = __esm(async () => {
|
|
122932
123302
|
init_drizzle_orm();
|
|
@@ -122934,7 +123304,6 @@ var init_timeback_assessments_service = __esm(async () => {
|
|
|
122934
123304
|
init_tables_index();
|
|
122935
123305
|
init_spans();
|
|
122936
123306
|
init_assessment_runtime2();
|
|
122937
|
-
init_qti();
|
|
122938
123307
|
init_timeback3();
|
|
122939
123308
|
init_errors2();
|
|
122940
123309
|
init_timeback_assessment_import_util();
|
|
@@ -122942,6 +123311,7 @@ var init_timeback_assessments_service = __esm(async () => {
|
|
|
122942
123311
|
init_timeback_assessment_runtime_util();
|
|
122943
123312
|
init_timeback_qti_authoring_util();
|
|
122944
123313
|
init_timeback_qti_hydration_util();
|
|
123314
|
+
init_timeback_review_mapping_util();
|
|
122945
123315
|
await init_errors8();
|
|
122946
123316
|
});
|
|
122947
123317
|
function buildTimebackBaseConfigFromExistingConfig(config5) {
|
|
@@ -185557,6 +185927,7 @@ var listAssessments;
|
|
|
185557
185927
|
var createAssessment;
|
|
185558
185928
|
var attachExistingAssessments;
|
|
185559
185929
|
var updateAssessment;
|
|
185930
|
+
var updateReviewMapping;
|
|
185560
185931
|
var reorderAssessments;
|
|
185561
185932
|
var reorderQuestions;
|
|
185562
185933
|
var removeAssessment;
|
|
@@ -186241,6 +186612,14 @@ var init_timeback_controller = __esm(() => {
|
|
|
186241
186612
|
const body2 = await parseRequestBody(ctx.request, UpdateAssessmentRequestSchema);
|
|
186242
186613
|
return ctx.services.timebackAssessments.updateAssessment(integrationId, testIdentifier, body2);
|
|
186243
186614
|
});
|
|
186615
|
+
updateReviewMapping = requireDeveloper(async (ctx) => {
|
|
186616
|
+
const { gameId, courseId, testIdentifier } = ctx.params;
|
|
186617
|
+
if (!gameId || !courseId || !testIdentifier) {
|
|
186618
|
+
throw ApiError.badRequest("Missing gameId, courseId, or testIdentifier parameter");
|
|
186619
|
+
}
|
|
186620
|
+
const integrationId = await ctx.services.timebackAssessments.resolveIntegrationId(gameId, courseId, ctx.user);
|
|
186621
|
+
return ctx.services.timebackAssessments.updateReviewMapping(integrationId, testIdentifier);
|
|
186622
|
+
});
|
|
186244
186623
|
reorderAssessments = requireDeveloper(async (ctx) => {
|
|
186245
186624
|
const { gameId, courseId } = ctx.params;
|
|
186246
186625
|
if (!gameId || !courseId) {
|
|
@@ -186383,6 +186762,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
186383
186762
|
createAssessment,
|
|
186384
186763
|
attachExistingAssessments,
|
|
186385
186764
|
updateAssessment,
|
|
186765
|
+
updateReviewMapping,
|
|
186386
186766
|
reorderAssessments,
|
|
186387
186767
|
removeAssessment,
|
|
186388
186768
|
reorderQuestions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcademy/vite-plugin",
|
|
3
|
-
"version": "1.2.1-beta.
|
|
3
|
+
"version": "1.2.1-beta.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"archiver": "^7.0.1",
|
|
21
21
|
"picocolors": "^1.1.1",
|
|
22
|
-
"playcademy": "0.28.1-beta.
|
|
22
|
+
"playcademy": "0.28.1-beta.25"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@electric-sql/pglite": "^0.3.16",
|
|
26
26
|
"@inquirer/prompts": "^7.8.6",
|
|
27
27
|
"@playcademy/constants": "0.0.1",
|
|
28
|
-
"@playcademy/sandbox": "0.7.1-beta.
|
|
29
|
-
"@playcademy/sdk": "0.16.1-beta.
|
|
28
|
+
"@playcademy/sandbox": "0.7.1-beta.23",
|
|
29
|
+
"@playcademy/sdk": "0.16.1-beta.24",
|
|
30
30
|
"@playcademy/types": "0.0.1",
|
|
31
31
|
"@playcademy/utils": "0.0.1",
|
|
32
32
|
"@types/archiver": "^6.0.3",
|