@playcademy/sandbox 0.4.2-beta.2 → 0.4.2-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1033 -202
- package/dist/server.js +1033 -202
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1078,7 +1078,7 @@ var package_default;
|
|
|
1078
1078
|
var init_package = __esm(() => {
|
|
1079
1079
|
package_default = {
|
|
1080
1080
|
name: "@playcademy/sandbox",
|
|
1081
|
-
version: "0.4.2-beta.
|
|
1081
|
+
version: "0.4.2-beta.4",
|
|
1082
1082
|
description: "Local development server for Playcademy game development",
|
|
1083
1083
|
type: "module",
|
|
1084
1084
|
exports: {
|
|
@@ -11320,10 +11320,11 @@ var init_table6 = __esm(() => {
|
|
|
11320
11320
|
});
|
|
11321
11321
|
|
|
11322
11322
|
// ../data/src/domains/timeback/table.ts
|
|
11323
|
-
var gameTimebackIntegrations, gameTimebackAssessmentTests;
|
|
11323
|
+
var gameTimebackIntegrations, gameTimebackAssessmentTests, gameTimebackMetricDiscrepancyVerifications;
|
|
11324
11324
|
var init_table7 = __esm(() => {
|
|
11325
11325
|
init_pg_core();
|
|
11326
11326
|
init_table5();
|
|
11327
|
+
init_table3();
|
|
11327
11328
|
gameTimebackIntegrations = pgTable("game_timeback_integrations", {
|
|
11328
11329
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
11329
11330
|
gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
|
|
@@ -11348,6 +11349,21 @@ var init_table7 = __esm(() => {
|
|
|
11348
11349
|
}, (table3) => [
|
|
11349
11350
|
uniqueIndex("game_timeback_assessment_tests_integration_qti_idx").on(table3.integrationId, table3.qtiTestIdentifier)
|
|
11350
11351
|
]);
|
|
11352
|
+
gameTimebackMetricDiscrepancyVerifications = pgTable("game_timeback_metric_discrepancy_verifications", {
|
|
11353
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
11354
|
+
gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
|
|
11355
|
+
courseId: text("course_id").notNull(),
|
|
11356
|
+
studentId: text("student_id").notNull(),
|
|
11357
|
+
runId: uuid("run_id").notNull(),
|
|
11358
|
+
activityId: text("activity_id"),
|
|
11359
|
+
verifiedByUserId: text("verified_by_user_id").references(() => users.id, {
|
|
11360
|
+
onDelete: "set null"
|
|
11361
|
+
}),
|
|
11362
|
+
verifiedAt: timestamp("verified_at", { withTimezone: true }).notNull().defaultNow()
|
|
11363
|
+
}, (table3) => [
|
|
11364
|
+
uniqueIndex("game_timeback_metric_discrepancy_verifications_run_idx").on(table3.gameId, table3.courseId, table3.studentId, table3.runId),
|
|
11365
|
+
index("game_timeback_metric_discrepancy_verifications_course_idx").on(table3.gameId, table3.courseId, table3.verifiedAt)
|
|
11366
|
+
]);
|
|
11351
11367
|
});
|
|
11352
11368
|
|
|
11353
11369
|
// ../data/src/tables.index.ts
|
|
@@ -11361,6 +11377,7 @@ __export(exports_tables_index, {
|
|
|
11361
11377
|
games: () => games,
|
|
11362
11378
|
gameVisibilityEnum: () => gameVisibilityEnum,
|
|
11363
11379
|
gameTypeEnum: () => gameTypeEnum,
|
|
11380
|
+
gameTimebackMetricDiscrepancyVerifications: () => gameTimebackMetricDiscrepancyVerifications,
|
|
11364
11381
|
gameTimebackIntegrations: () => gameTimebackIntegrations,
|
|
11365
11382
|
gameTimebackAssessmentTests: () => gameTimebackAssessmentTests,
|
|
11366
11383
|
gameScoresRelations: () => gameScoresRelations,
|
|
@@ -29364,6 +29381,75 @@ function formatDateYMDInTimezone(timeZone, date3 = new Date) {
|
|
|
29364
29381
|
const d = parts2.find((p) => p.type === "day").value;
|
|
29365
29382
|
return `${y}-${m}-${d}`;
|
|
29366
29383
|
}
|
|
29384
|
+
function getUtcInstantForMidnight(date3, timeZone) {
|
|
29385
|
+
const parts2 = new Intl.DateTimeFormat("en-US", {
|
|
29386
|
+
timeZone,
|
|
29387
|
+
year: "numeric",
|
|
29388
|
+
month: "2-digit",
|
|
29389
|
+
day: "2-digit"
|
|
29390
|
+
}).formatToParts(date3).reduce((acc, p) => {
|
|
29391
|
+
if (p.type !== "literal") {
|
|
29392
|
+
acc[p.type] = p.value;
|
|
29393
|
+
}
|
|
29394
|
+
return acc;
|
|
29395
|
+
}, {});
|
|
29396
|
+
const year = Number(parts2.year);
|
|
29397
|
+
const month = Number(parts2.month);
|
|
29398
|
+
const day = Number(parts2.day);
|
|
29399
|
+
for (let dayOffset = -1;dayOffset <= 1; dayOffset++) {
|
|
29400
|
+
const testYear = year;
|
|
29401
|
+
const testMonth = month;
|
|
29402
|
+
const testDay = day + dayOffset;
|
|
29403
|
+
for (let utcHour = 0;utcHour < 24; utcHour++) {
|
|
29404
|
+
const testDate = new Date(Date.UTC(testYear, testMonth - 1, testDay, utcHour, 0, 0, 0));
|
|
29405
|
+
const testParts = new Intl.DateTimeFormat("en-US", {
|
|
29406
|
+
timeZone,
|
|
29407
|
+
year: "numeric",
|
|
29408
|
+
month: "2-digit",
|
|
29409
|
+
day: "2-digit",
|
|
29410
|
+
hour: "2-digit",
|
|
29411
|
+
minute: "2-digit",
|
|
29412
|
+
hour12: false
|
|
29413
|
+
}).formatToParts(testDate).reduce((acc, p) => {
|
|
29414
|
+
if (p.type !== "literal") {
|
|
29415
|
+
acc[p.type] = p.value;
|
|
29416
|
+
}
|
|
29417
|
+
return acc;
|
|
29418
|
+
}, {});
|
|
29419
|
+
const yearMatch = Number(testParts.year) === year;
|
|
29420
|
+
const monthMatch = Number(testParts.month) === month;
|
|
29421
|
+
const dayMatch = Number(testParts.day) === day;
|
|
29422
|
+
const hourValue = Number(testParts.hour);
|
|
29423
|
+
const hourMatch = hourValue === 0 || hourValue === 24;
|
|
29424
|
+
const minuteMatch = Number(testParts.minute) === 0;
|
|
29425
|
+
if (yearMatch && monthMatch && dayMatch && hourMatch && minuteMatch) {
|
|
29426
|
+
return testDate;
|
|
29427
|
+
}
|
|
29428
|
+
}
|
|
29429
|
+
}
|
|
29430
|
+
throw new Error(`Could not find midnight for ${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")} in timezone ${timeZone}`);
|
|
29431
|
+
}
|
|
29432
|
+
function getDayBoundariesInTimezone(date3, timezone) {
|
|
29433
|
+
const startOfDay = getUtcInstantForMidnight(date3, timezone);
|
|
29434
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
29435
|
+
timeZone: timezone,
|
|
29436
|
+
year: "numeric",
|
|
29437
|
+
month: "2-digit",
|
|
29438
|
+
day: "2-digit"
|
|
29439
|
+
});
|
|
29440
|
+
const parts2 = formatter.formatToParts(date3).reduce((acc, p) => {
|
|
29441
|
+
if (p.type !== "literal") {
|
|
29442
|
+
acc[p.type] = p.value;
|
|
29443
|
+
}
|
|
29444
|
+
return acc;
|
|
29445
|
+
}, {});
|
|
29446
|
+
const year = Number(parts2.year);
|
|
29447
|
+
const month = Number(parts2.month);
|
|
29448
|
+
const day = Number(parts2.day);
|
|
29449
|
+
const nextDayNoon = new Date(Date.UTC(year, month - 1, day + 1, 12, 0, 0, 0));
|
|
29450
|
+
const endOfDay = getUtcInstantForMidnight(nextDayNoon, timezone);
|
|
29451
|
+
return { startOfDay, endOfDay };
|
|
29452
|
+
}
|
|
29367
29453
|
// ../utils/src/url.ts
|
|
29368
29454
|
function buildPath(path, params) {
|
|
29369
29455
|
const url = new URL(path, "http://n");
|
|
@@ -29407,6 +29493,42 @@ function formatGradeLabel(grade) {
|
|
|
29407
29493
|
}
|
|
29408
29494
|
}
|
|
29409
29495
|
}
|
|
29496
|
+
function isTimebackDiscrepancyQueueWindow(value) {
|
|
29497
|
+
return typeof value === "string" && TIMEBACK_DISCREPANCY_QUEUE_WINDOW_VALUES.has(value);
|
|
29498
|
+
}
|
|
29499
|
+
function isTimebackDiscrepancyQueueMetric(value) {
|
|
29500
|
+
return typeof value === "string" && TIMEBACK_DISCREPANCY_QUEUE_METRIC_VALUES.has(value);
|
|
29501
|
+
}
|
|
29502
|
+
function parseTimebackDiscrepancyQueueWindow(value) {
|
|
29503
|
+
const window2 = value?.trim();
|
|
29504
|
+
return isTimebackDiscrepancyQueueWindow(window2) ? window2 : DEFAULT_TIMEBACK_DISCREPANCY_QUEUE_WINDOW;
|
|
29505
|
+
}
|
|
29506
|
+
function parseTimebackDiscrepancyQueueMetrics(values) {
|
|
29507
|
+
const metrics = [];
|
|
29508
|
+
const seen = new Set;
|
|
29509
|
+
for (const value of values) {
|
|
29510
|
+
const metric = value.trim();
|
|
29511
|
+
if (isTimebackDiscrepancyQueueMetric(metric) && !seen.has(metric)) {
|
|
29512
|
+
seen.add(metric);
|
|
29513
|
+
metrics.push(metric);
|
|
29514
|
+
}
|
|
29515
|
+
}
|
|
29516
|
+
return metrics;
|
|
29517
|
+
}
|
|
29518
|
+
var TIMEBACK_DISCREPANCY_QUEUE_WINDOWS, TIMEBACK_DISCREPANCY_QUEUE_METRICS, DEFAULT_TIMEBACK_DISCREPANCY_QUEUE_WINDOW = "this-week", TIMEBACK_DISCREPANCY_QUEUE_WINDOW_VALUES, TIMEBACK_DISCREPANCY_QUEUE_METRIC_VALUES;
|
|
29519
|
+
var init_timeback3 = __esm(() => {
|
|
29520
|
+
TIMEBACK_DISCREPANCY_QUEUE_WINDOWS = [
|
|
29521
|
+
"today",
|
|
29522
|
+
"yesterday",
|
|
29523
|
+
"this-week",
|
|
29524
|
+
"last-week",
|
|
29525
|
+
"all",
|
|
29526
|
+
"custom"
|
|
29527
|
+
];
|
|
29528
|
+
TIMEBACK_DISCREPANCY_QUEUE_METRICS = ["xp", "mastery", "time", "score"];
|
|
29529
|
+
TIMEBACK_DISCREPANCY_QUEUE_WINDOW_VALUES = new Set(TIMEBACK_DISCREPANCY_QUEUE_WINDOWS);
|
|
29530
|
+
TIMEBACK_DISCREPANCY_QUEUE_METRIC_VALUES = new Set(TIMEBACK_DISCREPANCY_QUEUE_METRICS);
|
|
29531
|
+
});
|
|
29410
29532
|
|
|
29411
29533
|
// ../../node_modules/.bun/drizzle-zod@0.7.1+e9f18f9688af15ce/node_modules/drizzle-zod/index.mjs
|
|
29412
29534
|
function isColumnType(column2, columnTypes) {
|
|
@@ -29907,7 +30029,7 @@ function isValidAdminAttributionDate(value) {
|
|
|
29907
30029
|
const date3 = new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
|
|
29908
30030
|
return date3.getUTCFullYear() === year && date3.getUTCMonth() + 1 === month && date3.getUTCDate() === day;
|
|
29909
30031
|
}
|
|
29910
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS4, TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, 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, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
30032
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS4, TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, 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, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
29911
30033
|
var init_schemas4 = __esm(() => {
|
|
29912
30034
|
init_drizzle_zod();
|
|
29913
30035
|
init_esm();
|
|
@@ -30148,6 +30270,11 @@ var init_schemas4 = __esm(() => {
|
|
|
30148
30270
|
studentId: exports_external.string().min(1),
|
|
30149
30271
|
enrollmentId: exports_external.string().min(1)
|
|
30150
30272
|
});
|
|
30273
|
+
VerifyTimebackMetricDiscrepancyRequestSchema = exports_external.object({
|
|
30274
|
+
studentId: exports_external.string().min(1),
|
|
30275
|
+
runId: exports_external.string().uuid(),
|
|
30276
|
+
activityId: exports_external.string().min(1).optional()
|
|
30277
|
+
});
|
|
30151
30278
|
InsertAssessmentTestSchema = createInsertSchema(gameTimebackAssessmentTests).omit({
|
|
30152
30279
|
id: true,
|
|
30153
30280
|
createdAt: true
|
|
@@ -30280,183 +30407,6 @@ var init_timeback_admin_util = __esm(() => {
|
|
|
30280
30407
|
init_errors();
|
|
30281
30408
|
});
|
|
30282
30409
|
|
|
30283
|
-
// ../api-core/src/utils/timeback-game-metrics-comparison.util.ts
|
|
30284
|
-
function createMetricRow(definition) {
|
|
30285
|
-
const { gameValue, kind, metric, timebackValue, tolerance } = definition;
|
|
30286
|
-
if (timebackValue === undefined && gameValue === undefined) {
|
|
30287
|
-
return null;
|
|
30288
|
-
}
|
|
30289
|
-
if (gameValue === undefined) {
|
|
30290
|
-
return {
|
|
30291
|
-
metric,
|
|
30292
|
-
kind,
|
|
30293
|
-
status: "not_reported_by_game",
|
|
30294
|
-
...timebackValue !== undefined ? { timebackValue } : {}
|
|
30295
|
-
};
|
|
30296
|
-
}
|
|
30297
|
-
if (timebackValue === undefined) {
|
|
30298
|
-
return {
|
|
30299
|
-
metric,
|
|
30300
|
-
kind,
|
|
30301
|
-
status: "not_recorded_by_timeback",
|
|
30302
|
-
gameValue
|
|
30303
|
-
};
|
|
30304
|
-
}
|
|
30305
|
-
const delta = gameValue - timebackValue;
|
|
30306
|
-
const isDiscrepant = tolerance === 0 ? delta !== 0 : Math.abs(delta) >= tolerance;
|
|
30307
|
-
return {
|
|
30308
|
-
metric,
|
|
30309
|
-
kind,
|
|
30310
|
-
status: isDiscrepant ? "discrepant" : "matched",
|
|
30311
|
-
timebackValue,
|
|
30312
|
-
gameValue,
|
|
30313
|
-
delta
|
|
30314
|
-
};
|
|
30315
|
-
}
|
|
30316
|
-
function createRunComparison(activity, gameRun) {
|
|
30317
|
-
const runId = activity.runId ?? "";
|
|
30318
|
-
if (!gameRun) {
|
|
30319
|
-
return {
|
|
30320
|
-
runId,
|
|
30321
|
-
status: "not_reported",
|
|
30322
|
-
discrepancyCount: 0,
|
|
30323
|
-
rows: []
|
|
30324
|
-
};
|
|
30325
|
-
}
|
|
30326
|
-
const rows = [
|
|
30327
|
-
createMetricRow({
|
|
30328
|
-
metric: "xp",
|
|
30329
|
-
kind: "number",
|
|
30330
|
-
timebackValue: activity.xpDelta,
|
|
30331
|
-
gameValue: gameRun.totalXp,
|
|
30332
|
-
tolerance: TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE.xp
|
|
30333
|
-
}),
|
|
30334
|
-
createMetricRow({
|
|
30335
|
-
metric: "mastery",
|
|
30336
|
-
kind: "number",
|
|
30337
|
-
timebackValue: activity.masteredUnitsDelta,
|
|
30338
|
-
gameValue: gameRun.masteredUnits,
|
|
30339
|
-
tolerance: TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE.mastery
|
|
30340
|
-
}),
|
|
30341
|
-
createMetricRow({
|
|
30342
|
-
metric: "time",
|
|
30343
|
-
kind: "time",
|
|
30344
|
-
timebackValue: activity.timeDeltaSeconds,
|
|
30345
|
-
gameValue: gameRun.activeTimeSeconds,
|
|
30346
|
-
tolerance: TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE.time
|
|
30347
|
-
}),
|
|
30348
|
-
createMetricRow({
|
|
30349
|
-
metric: "score",
|
|
30350
|
-
kind: "percent",
|
|
30351
|
-
timebackValue: activity.score,
|
|
30352
|
-
gameValue: gameRun.score,
|
|
30353
|
-
tolerance: TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE.score
|
|
30354
|
-
})
|
|
30355
|
-
].filter((row) => row !== null);
|
|
30356
|
-
const discrepancyCount = rows.filter((row) => row.status === "discrepant").length;
|
|
30357
|
-
return {
|
|
30358
|
-
runId,
|
|
30359
|
-
status: discrepancyCount > 0 ? "discrepant" : "matched",
|
|
30360
|
-
discrepancyCount,
|
|
30361
|
-
rows
|
|
30362
|
-
};
|
|
30363
|
-
}
|
|
30364
|
-
function summarizeGameRunMetricsComparison(comparison) {
|
|
30365
|
-
return {
|
|
30366
|
-
runId: comparison.runId,
|
|
30367
|
-
status: comparison.status,
|
|
30368
|
-
discrepancyCount: comparison.discrepancyCount,
|
|
30369
|
-
...comparison.reason ? { reason: comparison.reason } : {}
|
|
30370
|
-
};
|
|
30371
|
-
}
|
|
30372
|
-
function buildGameRunMetricComparisons(activities, course, response) {
|
|
30373
|
-
const activitiesWithRunIds = activities.filter((activity) => typeof activity.runId === "string" && activity.runId.length > 0);
|
|
30374
|
-
const comparisons = new Map;
|
|
30375
|
-
if (activitiesWithRunIds.length === 0) {
|
|
30376
|
-
return comparisons;
|
|
30377
|
-
}
|
|
30378
|
-
if (!response.supported) {
|
|
30379
|
-
for (const activity of activitiesWithRunIds) {
|
|
30380
|
-
comparisons.set(activity.runId, {
|
|
30381
|
-
runId: activity.runId,
|
|
30382
|
-
status: "unavailable",
|
|
30383
|
-
discrepancyCount: 0,
|
|
30384
|
-
reason: response.reason,
|
|
30385
|
-
rows: []
|
|
30386
|
-
});
|
|
30387
|
-
}
|
|
30388
|
-
return comparisons;
|
|
30389
|
-
}
|
|
30390
|
-
const gameCourseMetrics = response.metrics.courses.find((gameCourse) => gameCourse.grade === course.grade && gameCourse.subject === course.subject);
|
|
30391
|
-
const gameRunsById = new Map(gameCourseMetrics?.activities?.map((gameRun) => [gameRun.runId.toLowerCase(), gameRun]));
|
|
30392
|
-
for (const activity of activitiesWithRunIds) {
|
|
30393
|
-
comparisons.set(activity.runId, createRunComparison(activity, gameRunsById.get(activity.runId.toLowerCase())));
|
|
30394
|
-
}
|
|
30395
|
-
return comparisons;
|
|
30396
|
-
}
|
|
30397
|
-
var init_timeback_game_metrics_comparison_util = __esm(() => {
|
|
30398
|
-
init_src();
|
|
30399
|
-
});
|
|
30400
|
-
|
|
30401
|
-
// ../api-core/src/utils/timeback-mastery-completion.util.ts
|
|
30402
|
-
async function upsertMasteryCompletionEntry(params) {
|
|
30403
|
-
const { client, courseId, studentId, appName, action } = params;
|
|
30404
|
-
const ids = deriveSourcedIds(courseId);
|
|
30405
|
-
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
30406
|
-
const resultId = `${lineItemId}:${studentId}:completion`;
|
|
30407
|
-
if (action === "complete") {
|
|
30408
|
-
await client.oneroster.assessmentLineItems.findOrCreate(lineItemId, {
|
|
30409
|
-
sourcedId: lineItemId,
|
|
30410
|
-
title: "Mastery Completion",
|
|
30411
|
-
status: ONEROSTER_STATUS.active,
|
|
30412
|
-
course: { sourcedId: ids.course },
|
|
30413
|
-
...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
|
|
30414
|
-
});
|
|
30415
|
-
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
30416
|
-
sourcedId: resultId,
|
|
30417
|
-
status: ONEROSTER_STATUS.active,
|
|
30418
|
-
assessmentLineItem: { sourcedId: lineItemId },
|
|
30419
|
-
student: { sourcedId: studentId },
|
|
30420
|
-
score: 100,
|
|
30421
|
-
scoreDate: new Date().toISOString(),
|
|
30422
|
-
scoreStatus: SCORE_STATUS.fullyGraded,
|
|
30423
|
-
inProgress: "false",
|
|
30424
|
-
metadata: {
|
|
30425
|
-
isMasteryCompletion: true,
|
|
30426
|
-
adminAction: true,
|
|
30427
|
-
appName
|
|
30428
|
-
}
|
|
30429
|
-
});
|
|
30430
|
-
} else {
|
|
30431
|
-
try {
|
|
30432
|
-
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
30433
|
-
sourcedId: resultId,
|
|
30434
|
-
status: ONEROSTER_STATUS.active,
|
|
30435
|
-
assessmentLineItem: { sourcedId: lineItemId },
|
|
30436
|
-
student: { sourcedId: studentId },
|
|
30437
|
-
score: 0,
|
|
30438
|
-
scoreDate: new Date().toISOString(),
|
|
30439
|
-
scoreStatus: SCORE_STATUS.notSubmitted,
|
|
30440
|
-
inProgress: "true",
|
|
30441
|
-
metadata: {
|
|
30442
|
-
isMasteryCompletion: true,
|
|
30443
|
-
adminAction: true,
|
|
30444
|
-
appName
|
|
30445
|
-
}
|
|
30446
|
-
});
|
|
30447
|
-
} catch {
|
|
30448
|
-
logger16.debug("No completion entry to revoke", { studentId, courseId });
|
|
30449
|
-
}
|
|
30450
|
-
}
|
|
30451
|
-
}
|
|
30452
|
-
var logger16;
|
|
30453
|
-
var init_timeback_mastery_completion_util = __esm(() => {
|
|
30454
|
-
init_src2();
|
|
30455
|
-
init_constants4();
|
|
30456
|
-
init_utils6();
|
|
30457
|
-
logger16 = log.scope("timeback-mastery-completion");
|
|
30458
|
-
});
|
|
30459
|
-
|
|
30460
30410
|
// ../api-core/src/utils/timeback.util.ts
|
|
30461
30411
|
function isRecord2(value) {
|
|
30462
30412
|
return typeof value === "object" && value !== null;
|
|
@@ -30604,6 +30554,13 @@ function groupCaliperEventsByRun(events) {
|
|
|
30604
30554
|
}
|
|
30605
30555
|
return groups;
|
|
30606
30556
|
}
|
|
30557
|
+
function findCaliperEventGroupContainingExternalId(events, externalId) {
|
|
30558
|
+
const targetExternalId = externalId.trim();
|
|
30559
|
+
if (!targetExternalId) {
|
|
30560
|
+
return;
|
|
30561
|
+
}
|
|
30562
|
+
return [...groupCaliperEventsByRun(events).values()].find((group) => group.some((event) => event.externalId === targetExternalId));
|
|
30563
|
+
}
|
|
30607
30564
|
function mapCaliperEventGroupToActivity(events, relevantCourseIds) {
|
|
30608
30565
|
if (events.length === 0) {
|
|
30609
30566
|
return null;
|
|
@@ -30777,6 +30734,371 @@ var init_timeback_util = __esm(() => {
|
|
|
30777
30734
|
]);
|
|
30778
30735
|
});
|
|
30779
30736
|
|
|
30737
|
+
// ../api-core/src/utils/timeback-discrepancy-queue.util.ts
|
|
30738
|
+
function parseDateInputParts(value) {
|
|
30739
|
+
const parts2 = value.split("-");
|
|
30740
|
+
return {
|
|
30741
|
+
year: Number(parts2[0]),
|
|
30742
|
+
month: Number(parts2[1]),
|
|
30743
|
+
day: Number(parts2[2])
|
|
30744
|
+
};
|
|
30745
|
+
}
|
|
30746
|
+
function addDateInputDays(value, days) {
|
|
30747
|
+
const { year, month, day } = parseDateInputParts(value);
|
|
30748
|
+
const date3 = new Date(Date.UTC(year, month - 1, day + days, 12));
|
|
30749
|
+
return [
|
|
30750
|
+
String(date3.getUTCFullYear()),
|
|
30751
|
+
String(date3.getUTCMonth() + 1).padStart(2, "0"),
|
|
30752
|
+
String(date3.getUTCDate()).padStart(2, "0")
|
|
30753
|
+
].join("-");
|
|
30754
|
+
}
|
|
30755
|
+
function getDateInputDayOfWeek(value) {
|
|
30756
|
+
const { year, month, day } = parseDateInputParts(value);
|
|
30757
|
+
return new Date(Date.UTC(year, month - 1, day, 12)).getUTCDay();
|
|
30758
|
+
}
|
|
30759
|
+
function getDateInputBoundary(value, boundary, timezone2) {
|
|
30760
|
+
const { year, month, day } = parseDateInputParts(value);
|
|
30761
|
+
const anchor = new Date(Date.UTC(year, month - 1, day, 12));
|
|
30762
|
+
const { startOfDay, endOfDay } = getDayBoundariesInTimezone(anchor, timezone2);
|
|
30763
|
+
return boundary === "end" ? new Date(endOfDay.getTime() - 1) : startOfDay;
|
|
30764
|
+
}
|
|
30765
|
+
function getWeekStartDateInput(date3, timezone2) {
|
|
30766
|
+
const today = formatDateYMDInTimezone(timezone2, date3);
|
|
30767
|
+
const dayOfWeek = getDateInputDayOfWeek(today);
|
|
30768
|
+
return addDateInputDays(today, -dayOfWeek);
|
|
30769
|
+
}
|
|
30770
|
+
function parseDateBoundary(value, boundary, timezone2) {
|
|
30771
|
+
if (!value) {
|
|
30772
|
+
return null;
|
|
30773
|
+
}
|
|
30774
|
+
if (DATE_INPUT_RE.test(value)) {
|
|
30775
|
+
return getDateInputBoundary(value, boundary, timezone2);
|
|
30776
|
+
}
|
|
30777
|
+
const parsed = new Date(value);
|
|
30778
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
30779
|
+
}
|
|
30780
|
+
function getTimebackDiscrepancyQueueDateRange(options) {
|
|
30781
|
+
const now2 = options.now ?? new Date;
|
|
30782
|
+
const timezone2 = options.timezone ?? "UTC";
|
|
30783
|
+
if (options.window === "all") {
|
|
30784
|
+
return {};
|
|
30785
|
+
}
|
|
30786
|
+
if (options.window === "today") {
|
|
30787
|
+
return { startDate: getDayBoundariesInTimezone(now2, timezone2).startOfDay.toISOString() };
|
|
30788
|
+
}
|
|
30789
|
+
if (options.window === "yesterday") {
|
|
30790
|
+
const yesterdayInput = addDateInputDays(formatDateYMDInTimezone(timezone2, now2), -1);
|
|
30791
|
+
return {
|
|
30792
|
+
startDate: getDateInputBoundary(yesterdayInput, "start", timezone2).toISOString(),
|
|
30793
|
+
endDate: getDateInputBoundary(yesterdayInput, "end", timezone2).toISOString()
|
|
30794
|
+
};
|
|
30795
|
+
}
|
|
30796
|
+
if (options.window === "this-week") {
|
|
30797
|
+
return {
|
|
30798
|
+
startDate: getDateInputBoundary(getWeekStartDateInput(now2, timezone2), "start", timezone2).toISOString()
|
|
30799
|
+
};
|
|
30800
|
+
}
|
|
30801
|
+
if (options.window === "last-week") {
|
|
30802
|
+
const thisWeekStartInput = getWeekStartDateInput(now2, timezone2);
|
|
30803
|
+
const lastWeekStartInput = addDateInputDays(thisWeekStartInput, -7);
|
|
30804
|
+
const lastWeekEndInput = addDateInputDays(thisWeekStartInput, -1);
|
|
30805
|
+
return {
|
|
30806
|
+
startDate: getDateInputBoundary(lastWeekStartInput, "start", timezone2).toISOString(),
|
|
30807
|
+
endDate: getDateInputBoundary(lastWeekEndInput, "end", timezone2).toISOString()
|
|
30808
|
+
};
|
|
30809
|
+
}
|
|
30810
|
+
const startDate = parseDateBoundary(options.startDate, "start", timezone2);
|
|
30811
|
+
const endDate = parseDateBoundary(options.endDate, "end", timezone2);
|
|
30812
|
+
return {
|
|
30813
|
+
...startDate ? { startDate: startDate.toISOString() } : {},
|
|
30814
|
+
...endDate ? { endDate: endDate.toISOString() } : {}
|
|
30815
|
+
};
|
|
30816
|
+
}
|
|
30817
|
+
function getCaliperActorSourcedId(event) {
|
|
30818
|
+
const actorId = typeof event.actor.id === "string" ? event.actor.id.trim() : "";
|
|
30819
|
+
if (!actorId) {
|
|
30820
|
+
return;
|
|
30821
|
+
}
|
|
30822
|
+
const normalized = actorId.replace(/\/$/, "");
|
|
30823
|
+
const segment = normalized.split("/").at(-1);
|
|
30824
|
+
if (!segment) {
|
|
30825
|
+
return;
|
|
30826
|
+
}
|
|
30827
|
+
try {
|
|
30828
|
+
return decodeURIComponent(segment);
|
|
30829
|
+
} catch {
|
|
30830
|
+
return segment;
|
|
30831
|
+
}
|
|
30832
|
+
}
|
|
30833
|
+
function getTimebackDiscrepancyVerificationKey(params) {
|
|
30834
|
+
return `${params.studentId}:${params.runId.toLowerCase()}`;
|
|
30835
|
+
}
|
|
30836
|
+
function getTimebackMetricDiscrepancyVerificationForActivity(params) {
|
|
30837
|
+
if (params.comparison?.status !== "discrepant" || !params.activity.runId) {
|
|
30838
|
+
return;
|
|
30839
|
+
}
|
|
30840
|
+
return params.verificationsByKey.get(getTimebackDiscrepancyVerificationKey({
|
|
30841
|
+
studentId: params.studentId,
|
|
30842
|
+
runId: params.activity.runId
|
|
30843
|
+
}));
|
|
30844
|
+
}
|
|
30845
|
+
function mapCaliperEventsToStudentGameplayActivities(events, relevantCourseIds) {
|
|
30846
|
+
if (relevantCourseIds.size === 0) {
|
|
30847
|
+
return [];
|
|
30848
|
+
}
|
|
30849
|
+
const gameplayEventsByStudent = new Map;
|
|
30850
|
+
for (const event of events) {
|
|
30851
|
+
const isGameplayEvent = event.type === "ActivityEvent" || event.type === "TimeSpentEvent";
|
|
30852
|
+
const studentId = isGameplayEvent && !isCaliperRemediationOrCompletionEvent(event) ? getCaliperActorSourcedId(event) : undefined;
|
|
30853
|
+
if (studentId) {
|
|
30854
|
+
const existing = gameplayEventsByStudent.get(studentId);
|
|
30855
|
+
if (existing) {
|
|
30856
|
+
existing.push(event);
|
|
30857
|
+
} else {
|
|
30858
|
+
gameplayEventsByStudent.set(studentId, [event]);
|
|
30859
|
+
}
|
|
30860
|
+
}
|
|
30861
|
+
}
|
|
30862
|
+
const groups = [];
|
|
30863
|
+
for (const [studentId, studentEvents] of gameplayEventsByStudent) {
|
|
30864
|
+
const activities = [...groupCaliperEventsByRun(studentEvents).values()].map((group) => mapCaliperEventGroupToActivity(group, relevantCourseIds)).filter((activity) => Boolean(activity)).toSorted((a, b) => b.occurredAt.localeCompare(a.occurredAt));
|
|
30865
|
+
if (activities.length > 0) {
|
|
30866
|
+
groups.push({ studentId, activities });
|
|
30867
|
+
}
|
|
30868
|
+
}
|
|
30869
|
+
return groups.toSorted((a, b) => {
|
|
30870
|
+
const aLatest = a.activities[0]?.occurredAt ?? "";
|
|
30871
|
+
const bLatest = b.activities[0]?.occurredAt ?? "";
|
|
30872
|
+
return bLatest.localeCompare(aLatest);
|
|
30873
|
+
});
|
|
30874
|
+
}
|
|
30875
|
+
function mapCaliperEventsToCompletedStudentGameplayActivities(events, relevantCourseIds, options = {}) {
|
|
30876
|
+
const runIds = options.runIds ? new Set([...options.runIds].map((runId) => runId.toLowerCase())) : undefined;
|
|
30877
|
+
return mapCaliperEventsToStudentGameplayActivities(events, relevantCourseIds).filter((group) => !options.studentId || group.studentId === options.studentId).map((group) => ({
|
|
30878
|
+
studentId: group.studentId,
|
|
30879
|
+
activities: group.activities.filter((activity) => {
|
|
30880
|
+
const runId = activity.runId?.toLowerCase();
|
|
30881
|
+
return activity.kind === "activity" && runId !== undefined && isValidUUID(runId) && (!runIds || runIds.has(runId));
|
|
30882
|
+
})
|
|
30883
|
+
})).filter((group) => group.activities.length > 0);
|
|
30884
|
+
}
|
|
30885
|
+
function getTimebackActivityRunIds(groups) {
|
|
30886
|
+
const runIds = [];
|
|
30887
|
+
const seen = new Set;
|
|
30888
|
+
for (const group of groups) {
|
|
30889
|
+
for (const activity of group.activities) {
|
|
30890
|
+
const runId = activity.runId?.toLowerCase();
|
|
30891
|
+
if (runId && isValidUUID(runId) && !seen.has(runId)) {
|
|
30892
|
+
seen.add(runId);
|
|
30893
|
+
runIds.push(runId);
|
|
30894
|
+
}
|
|
30895
|
+
}
|
|
30896
|
+
}
|
|
30897
|
+
return runIds;
|
|
30898
|
+
}
|
|
30899
|
+
function mergeHydratedMetricDiscrepancyQueueItems(options) {
|
|
30900
|
+
const hydratedRunIds = new Set([...options.hydratedRunIds].map((runId) => runId.toLowerCase()));
|
|
30901
|
+
const hydratedItems = options.hydratedItems.map((item) => ({
|
|
30902
|
+
...item,
|
|
30903
|
+
comparisonSource: "hydrated"
|
|
30904
|
+
}));
|
|
30905
|
+
const preliminaryFallbackItems = options.preliminaryItems.filter((item) => {
|
|
30906
|
+
const runId = item.activity.runId?.toLowerCase();
|
|
30907
|
+
return runId === undefined || !hydratedRunIds.has(runId);
|
|
30908
|
+
}).map((item) => ({
|
|
30909
|
+
...item,
|
|
30910
|
+
comparisonSource: "preliminary"
|
|
30911
|
+
}));
|
|
30912
|
+
return [...hydratedItems, ...preliminaryFallbackItems].toSorted((a, b) => b.activity.occurredAt.localeCompare(a.activity.occurredAt));
|
|
30913
|
+
}
|
|
30914
|
+
function selectTimebackMetricDiscrepancyQueueItems(candidates, options) {
|
|
30915
|
+
const discrepancyMetricScopesSet = new Set(options.discrepancyMetricScopes);
|
|
30916
|
+
return candidates.filter((candidate) => candidate.activity.kind === "activity").filter((candidate) => candidate.gameMetricsComparison.status === "discrepant").filter((candidate) => !options.studentId || candidate.student.studentId === options.studentId).filter((candidate) => discrepancyMetricScopesSet.size === 0 || candidate.gameMetricsComparison.rows.some((row) => row.status === "discrepant" && discrepancyMetricScopesSet.has(row.metric))).filter((candidate) => options.includeVerified || !candidate.verification).toSorted((a, b) => b.activity.occurredAt.localeCompare(a.activity.occurredAt));
|
|
30917
|
+
}
|
|
30918
|
+
var DATE_INPUT_RE;
|
|
30919
|
+
var init_timeback_discrepancy_queue_util = __esm(() => {
|
|
30920
|
+
init_src4();
|
|
30921
|
+
init_timeback_util();
|
|
30922
|
+
DATE_INPUT_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
30923
|
+
});
|
|
30924
|
+
|
|
30925
|
+
// ../api-core/src/utils/timeback-game-metrics-comparison.util.ts
|
|
30926
|
+
function createMetricRow(definition) {
|
|
30927
|
+
const { gameValue, kind, metric, timebackValue, tolerance } = definition;
|
|
30928
|
+
if (timebackValue === undefined && gameValue === undefined) {
|
|
30929
|
+
return null;
|
|
30930
|
+
}
|
|
30931
|
+
if (gameValue === undefined) {
|
|
30932
|
+
return {
|
|
30933
|
+
metric,
|
|
30934
|
+
kind,
|
|
30935
|
+
status: "not_reported_by_game",
|
|
30936
|
+
...timebackValue !== undefined ? { timebackValue } : {}
|
|
30937
|
+
};
|
|
30938
|
+
}
|
|
30939
|
+
if (timebackValue === undefined) {
|
|
30940
|
+
return {
|
|
30941
|
+
metric,
|
|
30942
|
+
kind,
|
|
30943
|
+
status: "not_recorded_by_timeback",
|
|
30944
|
+
gameValue
|
|
30945
|
+
};
|
|
30946
|
+
}
|
|
30947
|
+
const delta = gameValue - timebackValue;
|
|
30948
|
+
const isDiscrepant = tolerance === 0 ? delta !== 0 : Math.abs(delta) >= tolerance;
|
|
30949
|
+
return {
|
|
30950
|
+
metric,
|
|
30951
|
+
kind,
|
|
30952
|
+
status: isDiscrepant ? "discrepant" : "matched",
|
|
30953
|
+
timebackValue,
|
|
30954
|
+
gameValue,
|
|
30955
|
+
delta
|
|
30956
|
+
};
|
|
30957
|
+
}
|
|
30958
|
+
function createRunComparison(activity, gameRun) {
|
|
30959
|
+
const runId = activity.runId ?? "";
|
|
30960
|
+
if (!gameRun) {
|
|
30961
|
+
return {
|
|
30962
|
+
runId,
|
|
30963
|
+
status: "not_reported",
|
|
30964
|
+
discrepancyCount: 0,
|
|
30965
|
+
rows: []
|
|
30966
|
+
};
|
|
30967
|
+
}
|
|
30968
|
+
const rows = [
|
|
30969
|
+
createMetricRow({
|
|
30970
|
+
metric: "xp",
|
|
30971
|
+
kind: "number",
|
|
30972
|
+
timebackValue: activity.xpDelta,
|
|
30973
|
+
gameValue: gameRun.totalXp,
|
|
30974
|
+
tolerance: TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE.xp
|
|
30975
|
+
}),
|
|
30976
|
+
createMetricRow({
|
|
30977
|
+
metric: "time",
|
|
30978
|
+
kind: "time",
|
|
30979
|
+
timebackValue: activity.timeDeltaSeconds,
|
|
30980
|
+
gameValue: gameRun.activeTimeSeconds,
|
|
30981
|
+
tolerance: TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE.time
|
|
30982
|
+
}),
|
|
30983
|
+
createMetricRow({
|
|
30984
|
+
metric: "score",
|
|
30985
|
+
kind: "percent",
|
|
30986
|
+
timebackValue: activity.score,
|
|
30987
|
+
gameValue: gameRun.score,
|
|
30988
|
+
tolerance: TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE.score
|
|
30989
|
+
}),
|
|
30990
|
+
createMetricRow({
|
|
30991
|
+
metric: "mastery",
|
|
30992
|
+
kind: "number",
|
|
30993
|
+
timebackValue: activity.masteredUnitsDelta ?? 0,
|
|
30994
|
+
gameValue: gameRun.masteredUnits,
|
|
30995
|
+
tolerance: TIMEBACK_GAME_METRIC_COMPARISON_TOLERANCE.mastery
|
|
30996
|
+
})
|
|
30997
|
+
].filter((row) => row !== null);
|
|
30998
|
+
const discrepancyCount = rows.filter((row) => row.status === "discrepant").length;
|
|
30999
|
+
return {
|
|
31000
|
+
runId,
|
|
31001
|
+
status: discrepancyCount > 0 ? "discrepant" : "matched",
|
|
31002
|
+
discrepancyCount,
|
|
31003
|
+
rows
|
|
31004
|
+
};
|
|
31005
|
+
}
|
|
31006
|
+
function summarizeGameRunMetricsComparison(comparison) {
|
|
31007
|
+
return {
|
|
31008
|
+
runId: comparison.runId,
|
|
31009
|
+
status: comparison.status,
|
|
31010
|
+
discrepancyCount: comparison.discrepancyCount,
|
|
31011
|
+
...comparison.reason ? { reason: comparison.reason } : {}
|
|
31012
|
+
};
|
|
31013
|
+
}
|
|
31014
|
+
function buildGameRunMetricComparisons(activities, course, response) {
|
|
31015
|
+
const activitiesWithRunIds = activities.filter((activity) => typeof activity.runId === "string" && activity.runId.length > 0);
|
|
31016
|
+
const comparisons = new Map;
|
|
31017
|
+
if (activitiesWithRunIds.length === 0) {
|
|
31018
|
+
return comparisons;
|
|
31019
|
+
}
|
|
31020
|
+
if (!response.supported) {
|
|
31021
|
+
for (const activity of activitiesWithRunIds) {
|
|
31022
|
+
comparisons.set(activity.runId, {
|
|
31023
|
+
runId: activity.runId,
|
|
31024
|
+
status: "unavailable",
|
|
31025
|
+
discrepancyCount: 0,
|
|
31026
|
+
reason: response.reason,
|
|
31027
|
+
rows: []
|
|
31028
|
+
});
|
|
31029
|
+
}
|
|
31030
|
+
return comparisons;
|
|
31031
|
+
}
|
|
31032
|
+
const gameCourseMetrics = response.metrics.courses.find((gameCourse) => gameCourse.grade === course.grade && gameCourse.subject === course.subject);
|
|
31033
|
+
const gameRunsById = new Map(gameCourseMetrics?.activities?.map((gameRun) => [gameRun.runId.toLowerCase(), gameRun]));
|
|
31034
|
+
for (const activity of activitiesWithRunIds) {
|
|
31035
|
+
comparisons.set(activity.runId, createRunComparison(activity, gameRunsById.get(activity.runId.toLowerCase())));
|
|
31036
|
+
}
|
|
31037
|
+
return comparisons;
|
|
31038
|
+
}
|
|
31039
|
+
var init_timeback_game_metrics_comparison_util = __esm(() => {
|
|
31040
|
+
init_src();
|
|
31041
|
+
});
|
|
31042
|
+
|
|
31043
|
+
// ../api-core/src/utils/timeback-mastery-completion.util.ts
|
|
31044
|
+
async function upsertMasteryCompletionEntry(params) {
|
|
31045
|
+
const { client, courseId, studentId, appName, action } = params;
|
|
31046
|
+
const ids = deriveSourcedIds(courseId);
|
|
31047
|
+
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
31048
|
+
const resultId = `${lineItemId}:${studentId}:completion`;
|
|
31049
|
+
if (action === "complete") {
|
|
31050
|
+
await client.oneroster.assessmentLineItems.findOrCreate(lineItemId, {
|
|
31051
|
+
sourcedId: lineItemId,
|
|
31052
|
+
title: "Mastery Completion",
|
|
31053
|
+
status: ONEROSTER_STATUS.active,
|
|
31054
|
+
course: { sourcedId: ids.course },
|
|
31055
|
+
...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
|
|
31056
|
+
});
|
|
31057
|
+
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31058
|
+
sourcedId: resultId,
|
|
31059
|
+
status: ONEROSTER_STATUS.active,
|
|
31060
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
31061
|
+
student: { sourcedId: studentId },
|
|
31062
|
+
score: 100,
|
|
31063
|
+
scoreDate: new Date().toISOString(),
|
|
31064
|
+
scoreStatus: SCORE_STATUS.fullyGraded,
|
|
31065
|
+
inProgress: "false",
|
|
31066
|
+
metadata: {
|
|
31067
|
+
isMasteryCompletion: true,
|
|
31068
|
+
adminAction: true,
|
|
31069
|
+
appName
|
|
31070
|
+
}
|
|
31071
|
+
});
|
|
31072
|
+
} else {
|
|
31073
|
+
try {
|
|
31074
|
+
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31075
|
+
sourcedId: resultId,
|
|
31076
|
+
status: ONEROSTER_STATUS.active,
|
|
31077
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
31078
|
+
student: { sourcedId: studentId },
|
|
31079
|
+
score: 0,
|
|
31080
|
+
scoreDate: new Date().toISOString(),
|
|
31081
|
+
scoreStatus: SCORE_STATUS.notSubmitted,
|
|
31082
|
+
inProgress: "true",
|
|
31083
|
+
metadata: {
|
|
31084
|
+
isMasteryCompletion: true,
|
|
31085
|
+
adminAction: true,
|
|
31086
|
+
appName
|
|
31087
|
+
}
|
|
31088
|
+
});
|
|
31089
|
+
} catch {
|
|
31090
|
+
logger16.debug("No completion entry to revoke", { studentId, courseId });
|
|
31091
|
+
}
|
|
31092
|
+
}
|
|
31093
|
+
}
|
|
31094
|
+
var logger16;
|
|
31095
|
+
var init_timeback_mastery_completion_util = __esm(() => {
|
|
31096
|
+
init_src2();
|
|
31097
|
+
init_constants4();
|
|
31098
|
+
init_utils6();
|
|
31099
|
+
logger16 = log.scope("timeback-mastery-completion");
|
|
31100
|
+
});
|
|
31101
|
+
|
|
30780
31102
|
// ../api-core/src/services/timeback-admin.service.ts
|
|
30781
31103
|
class TimebackAdminService {
|
|
30782
31104
|
deps;
|
|
@@ -30785,6 +31107,10 @@ class TimebackAdminService {
|
|
|
30785
31107
|
static MAX_STUDENT_ACTIVITY_LIMIT = 200;
|
|
30786
31108
|
static MAX_STUDENT_ACTIVITY_OFFSET = 1000;
|
|
30787
31109
|
static MAX_RECENT_ACTIVITY_EVENT_FETCH = 4000;
|
|
31110
|
+
static DISCREPANCY_QUEUE_EVENT_FETCH_LIMIT = 4000;
|
|
31111
|
+
static DISCREPANCY_QUEUE_RUN_EVENT_FETCH_LIMIT = 4000;
|
|
31112
|
+
static DISCREPANCY_QUEUE_RUN_HYDRATION_CONCURRENCY = 4;
|
|
31113
|
+
static DISCREPANCY_QUEUE_COMPARISON_CONCURRENCY = 4;
|
|
30788
31114
|
static ANALYTICS_CONCURRENCY = 8;
|
|
30789
31115
|
static MASTERABLE_UNITS_CONCURRENCY = 4;
|
|
30790
31116
|
static GAME_METRICS_FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -31032,16 +31358,53 @@ class TimebackAdminService {
|
|
|
31032
31358
|
}));
|
|
31033
31359
|
return comparisons;
|
|
31034
31360
|
}
|
|
31361
|
+
async getMetricDiscrepancyVerificationsByKey(options) {
|
|
31362
|
+
const runIds = TimebackAdminService.normalizeRunIds(options.runIds);
|
|
31363
|
+
if (runIds.length === 0) {
|
|
31364
|
+
return new Map;
|
|
31365
|
+
}
|
|
31366
|
+
const conditions2 = [
|
|
31367
|
+
eq(gameTimebackMetricDiscrepancyVerifications.gameId, options.gameId),
|
|
31368
|
+
eq(gameTimebackMetricDiscrepancyVerifications.courseId, options.courseId),
|
|
31369
|
+
inArray(gameTimebackMetricDiscrepancyVerifications.runId, runIds)
|
|
31370
|
+
];
|
|
31371
|
+
if (options.studentId) {
|
|
31372
|
+
conditions2.push(eq(gameTimebackMetricDiscrepancyVerifications.studentId, options.studentId));
|
|
31373
|
+
}
|
|
31374
|
+
const rows = await this.deps.db.query.gameTimebackMetricDiscrepancyVerifications.findMany({
|
|
31375
|
+
where: and(...conditions2)
|
|
31376
|
+
});
|
|
31377
|
+
return new Map(rows.map((row) => [
|
|
31378
|
+
getTimebackDiscrepancyVerificationKey({
|
|
31379
|
+
studentId: row.studentId,
|
|
31380
|
+
runId: row.runId
|
|
31381
|
+
}),
|
|
31382
|
+
TimebackAdminService.mapMetricDiscrepancyVerification(row)
|
|
31383
|
+
]));
|
|
31384
|
+
}
|
|
31035
31385
|
async attachGameMetricSummariesToActivities(user, options) {
|
|
31036
31386
|
const comparisons = await this.getGameMetricComparisonsForActivities(user, options);
|
|
31037
31387
|
if (comparisons.size === 0) {
|
|
31038
31388
|
return [...options.activities];
|
|
31039
31389
|
}
|
|
31390
|
+
const verificationsByKey = await this.getMetricDiscrepancyVerificationsByKey({
|
|
31391
|
+
gameId: options.gameId,
|
|
31392
|
+
courseId: options.courseId,
|
|
31393
|
+
studentId: options.studentId,
|
|
31394
|
+
runIds: [...comparisons.values()].filter((comparison) => comparison.status === "discrepant").map((comparison) => comparison.runId)
|
|
31395
|
+
});
|
|
31040
31396
|
return options.activities.map((activity) => {
|
|
31041
31397
|
const comparison = activity.runId ? comparisons.get(activity.runId) : undefined;
|
|
31398
|
+
const verification2 = getTimebackMetricDiscrepancyVerificationForActivity({
|
|
31399
|
+
studentId: options.studentId,
|
|
31400
|
+
activity,
|
|
31401
|
+
comparison,
|
|
31402
|
+
verificationsByKey
|
|
31403
|
+
});
|
|
31042
31404
|
return comparison ? {
|
|
31043
31405
|
...activity,
|
|
31044
|
-
gameMetricsComparison: summarizeGameRunMetricsComparison(comparison)
|
|
31406
|
+
gameMetricsComparison: summarizeGameRunMetricsComparison(comparison),
|
|
31407
|
+
...verification2 ? { gameMetricsVerification: verification2 } : {}
|
|
31045
31408
|
} : activity;
|
|
31046
31409
|
});
|
|
31047
31410
|
}
|
|
@@ -31132,6 +31495,165 @@ class TimebackAdminService {
|
|
|
31132
31495
|
});
|
|
31133
31496
|
return events;
|
|
31134
31497
|
}
|
|
31498
|
+
async fetchCaliperEventsForGame(client, source, options) {
|
|
31499
|
+
const actorId = options.studentId ? `${client.getBaseUrl().replace(/\/$/, "")}/ims/oneroster/rostering/v1p2/users/${options.studentId}` : undefined;
|
|
31500
|
+
return client.caliper.events.list({
|
|
31501
|
+
limit: options.limit,
|
|
31502
|
+
offset: options.offset,
|
|
31503
|
+
...actorId ? { actorId } : {},
|
|
31504
|
+
...options.startDate ? { startDate: options.startDate } : {},
|
|
31505
|
+
...options.endDate ? { endDate: options.endDate } : {},
|
|
31506
|
+
...options.sessionId ? { sessionId: options.sessionId } : {},
|
|
31507
|
+
...source.sourceMode === "production" ? { sensor: source.sensorUrl } : {},
|
|
31508
|
+
extensions: {
|
|
31509
|
+
gameId: source.gameId
|
|
31510
|
+
}
|
|
31511
|
+
});
|
|
31512
|
+
}
|
|
31513
|
+
static getActivityRunOwners(activityGroups) {
|
|
31514
|
+
const runOwnersById = new Map;
|
|
31515
|
+
for (const group of activityGroups) {
|
|
31516
|
+
for (const activity of group.activities) {
|
|
31517
|
+
const runId = activity.runId?.toLowerCase();
|
|
31518
|
+
if (runId && !runOwnersById.has(runId)) {
|
|
31519
|
+
runOwnersById.set(runId, group.studentId);
|
|
31520
|
+
}
|
|
31521
|
+
}
|
|
31522
|
+
}
|
|
31523
|
+
return runOwnersById;
|
|
31524
|
+
}
|
|
31525
|
+
static dedupeCaliperEvents(events) {
|
|
31526
|
+
const deduped = [];
|
|
31527
|
+
const seen = new Set;
|
|
31528
|
+
for (const event of events) {
|
|
31529
|
+
const key = `${event.id}:${event.externalId}`;
|
|
31530
|
+
if (!seen.has(key)) {
|
|
31531
|
+
seen.add(key);
|
|
31532
|
+
deduped.push(event);
|
|
31533
|
+
}
|
|
31534
|
+
}
|
|
31535
|
+
return deduped;
|
|
31536
|
+
}
|
|
31537
|
+
async fetchCaliperEventsForRun(client, source, options) {
|
|
31538
|
+
const runId = options.runId.toLowerCase();
|
|
31539
|
+
const events = [];
|
|
31540
|
+
let offset = 0;
|
|
31541
|
+
while (true) {
|
|
31542
|
+
const result = await this.fetchCaliperEventsForGame(client, source, {
|
|
31543
|
+
limit: TimebackAdminService.DISCREPANCY_QUEUE_RUN_EVENT_FETCH_LIMIT,
|
|
31544
|
+
offset,
|
|
31545
|
+
studentId: options.studentId
|
|
31546
|
+
});
|
|
31547
|
+
const runEvents = result.events.filter((event) => getCanonicalRunId(event.session)?.toLowerCase() === runId);
|
|
31548
|
+
events.push(...runEvents);
|
|
31549
|
+
const pageStep = result.pagination.limit > 0 ? result.pagination.limit : result.events.length;
|
|
31550
|
+
const nextOffset = offset + pageStep;
|
|
31551
|
+
if (pageStep === 0 || result.events.length === 0 || nextOffset >= result.pagination.total) {
|
|
31552
|
+
break;
|
|
31553
|
+
}
|
|
31554
|
+
offset = nextOffset;
|
|
31555
|
+
}
|
|
31556
|
+
return TimebackAdminService.dedupeCaliperEvents(events);
|
|
31557
|
+
}
|
|
31558
|
+
async hydrateDiscrepancyQueueRunEvents(client, source, options) {
|
|
31559
|
+
if (options.runOwnersById.size === 0) {
|
|
31560
|
+
return { events: [], runIds: new Set };
|
|
31561
|
+
}
|
|
31562
|
+
const hydratedRuns = await TimebackAdminService.runWithConcurrency([...options.runOwnersById.entries()], TimebackAdminService.DISCREPANCY_QUEUE_RUN_HYDRATION_CONCURRENCY, async ([runId, studentId]) => {
|
|
31563
|
+
try {
|
|
31564
|
+
return {
|
|
31565
|
+
runId,
|
|
31566
|
+
events: await this.fetchCaliperEventsForRun(client, source, {
|
|
31567
|
+
runId,
|
|
31568
|
+
studentId
|
|
31569
|
+
})
|
|
31570
|
+
};
|
|
31571
|
+
} catch (error) {
|
|
31572
|
+
logger17.warn("Failed to hydrate Caliper events for discrepancy queue run", {
|
|
31573
|
+
runId,
|
|
31574
|
+
studentId,
|
|
31575
|
+
error: error instanceof Error ? error.message : String(error)
|
|
31576
|
+
});
|
|
31577
|
+
return { runId, events: [] };
|
|
31578
|
+
}
|
|
31579
|
+
});
|
|
31580
|
+
const hydratedRunIds = new Set(hydratedRuns.filter((result) => result.events.length > 0).map((result) => result.runId.toLowerCase()));
|
|
31581
|
+
const baseEventsForHydratedRuns = options.baseEvents.filter((event) => {
|
|
31582
|
+
const runId = getCanonicalRunId(event.session)?.toLowerCase();
|
|
31583
|
+
return runId !== undefined && hydratedRunIds.has(runId);
|
|
31584
|
+
});
|
|
31585
|
+
return {
|
|
31586
|
+
events: TimebackAdminService.dedupeCaliperEvents([
|
|
31587
|
+
...baseEventsForHydratedRuns,
|
|
31588
|
+
...hydratedRuns.flatMap((result) => result.events)
|
|
31589
|
+
]),
|
|
31590
|
+
runIds: hydratedRunIds
|
|
31591
|
+
};
|
|
31592
|
+
}
|
|
31593
|
+
async buildMetricDiscrepancyQueueCandidates(user, options) {
|
|
31594
|
+
const comparisonResults = await TimebackAdminService.runWithConcurrency(options.activityGroups, TimebackAdminService.DISCREPANCY_QUEUE_COMPARISON_CONCURRENCY, async (group) => {
|
|
31595
|
+
try {
|
|
31596
|
+
return {
|
|
31597
|
+
group,
|
|
31598
|
+
comparisons: await this.getGameMetricComparisonsForActivities(user, {
|
|
31599
|
+
gameId: options.gameId,
|
|
31600
|
+
studentId: group.studentId,
|
|
31601
|
+
course: options.course,
|
|
31602
|
+
activities: group.activities,
|
|
31603
|
+
timeoutMs: TimebackAdminService.GAME_METRICS_LIST_FETCH_TIMEOUT_MS
|
|
31604
|
+
})
|
|
31605
|
+
};
|
|
31606
|
+
} catch (error) {
|
|
31607
|
+
logger17.warn("Failed to compare game metrics for discrepancy queue student", {
|
|
31608
|
+
gameId: options.gameId,
|
|
31609
|
+
courseId: options.courseId,
|
|
31610
|
+
studentId: group.studentId,
|
|
31611
|
+
error: error instanceof Error ? error.message : String(error)
|
|
31612
|
+
});
|
|
31613
|
+
return { group, comparisons: new Map };
|
|
31614
|
+
}
|
|
31615
|
+
});
|
|
31616
|
+
return comparisonResults.flatMap(({ group, comparisons }) => {
|
|
31617
|
+
const student = options.studentsById.get(group.studentId) ?? {
|
|
31618
|
+
studentId: group.studentId,
|
|
31619
|
+
name: "No name specified",
|
|
31620
|
+
email: null
|
|
31621
|
+
};
|
|
31622
|
+
return group.activities.flatMap((activity) => {
|
|
31623
|
+
const runId = activity.runId;
|
|
31624
|
+
if (!runId) {
|
|
31625
|
+
return [];
|
|
31626
|
+
}
|
|
31627
|
+
const gameMetricsComparison = comparisons.get(runId);
|
|
31628
|
+
if (!gameMetricsComparison) {
|
|
31629
|
+
return [];
|
|
31630
|
+
}
|
|
31631
|
+
const verification2 = options.verificationsByKey.get(getTimebackDiscrepancyVerificationKey({
|
|
31632
|
+
studentId: group.studentId,
|
|
31633
|
+
runId
|
|
31634
|
+
}));
|
|
31635
|
+
return [
|
|
31636
|
+
{
|
|
31637
|
+
student,
|
|
31638
|
+
activity,
|
|
31639
|
+
gameMetricsComparison,
|
|
31640
|
+
...verification2 ? { verification: verification2 } : {}
|
|
31641
|
+
}
|
|
31642
|
+
];
|
|
31643
|
+
});
|
|
31644
|
+
});
|
|
31645
|
+
}
|
|
31646
|
+
static mapMetricDiscrepancyVerification(row) {
|
|
31647
|
+
return {
|
|
31648
|
+
gameId: row.gameId,
|
|
31649
|
+
courseId: row.courseId,
|
|
31650
|
+
studentId: row.studentId,
|
|
31651
|
+
runId: row.runId,
|
|
31652
|
+
activityId: row.activityId,
|
|
31653
|
+
verifiedAt: row.verifiedAt.toISOString(),
|
|
31654
|
+
verifiedByUserId: row.verifiedByUserId
|
|
31655
|
+
};
|
|
31656
|
+
}
|
|
31135
31657
|
async listRecentActivityForStudent(client, studentId, source, relevantCourseIds, maxResults = TimebackAdminService.RECENT_ACTIVITY_LIMIT) {
|
|
31136
31658
|
if (relevantCourseIds.size === 0) {
|
|
31137
31659
|
return [];
|
|
@@ -31381,12 +31903,215 @@ class TimebackAdminService {
|
|
|
31381
31903
|
const activitiesWithGameMetrics = await this.attachGameMetricSummariesToActivities(user, {
|
|
31382
31904
|
gameId,
|
|
31383
31905
|
studentId,
|
|
31906
|
+
courseId,
|
|
31384
31907
|
course: { grade: integration.grade, subject: integration.subject },
|
|
31385
31908
|
activities,
|
|
31386
31909
|
timeoutMs: TimebackAdminService.GAME_METRICS_LIST_FETCH_TIMEOUT_MS
|
|
31387
31910
|
});
|
|
31388
31911
|
return { activities: activitiesWithGameMetrics, hasMore };
|
|
31389
31912
|
}
|
|
31913
|
+
async listMetricDiscrepancies(user, options) {
|
|
31914
|
+
const { gameId, courseId, window: window2, includeVerified } = options;
|
|
31915
|
+
const selectedStudentId = options.studentId?.trim() || undefined;
|
|
31916
|
+
const client = this.requireClient();
|
|
31917
|
+
const dateRange = getTimebackDiscrepancyQueueDateRange({
|
|
31918
|
+
window: window2,
|
|
31919
|
+
startDate: options.startDate,
|
|
31920
|
+
endDate: options.endDate,
|
|
31921
|
+
timezone: PLATFORM_TIMEZONE
|
|
31922
|
+
});
|
|
31923
|
+
const safeEventOffset = Math.max(0, Math.trunc(options.eventOffset));
|
|
31924
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
31925
|
+
const [integration, gameSource, roster] = await Promise.all([
|
|
31926
|
+
this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31927
|
+
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
31928
|
+
}),
|
|
31929
|
+
this.getGameActivitySource(gameId),
|
|
31930
|
+
client.oneroster.enrollments.listByCourse(courseId, {
|
|
31931
|
+
role: "student",
|
|
31932
|
+
includeInactive: true
|
|
31933
|
+
})
|
|
31934
|
+
]);
|
|
31935
|
+
if (!integration) {
|
|
31936
|
+
throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
|
|
31937
|
+
}
|
|
31938
|
+
const studentsById = new Map;
|
|
31939
|
+
for (const entry of roster) {
|
|
31940
|
+
const studentId = entry.enrollment.user.sourcedId;
|
|
31941
|
+
if (studentId && !studentsById.has(studentId)) {
|
|
31942
|
+
const name3 = entry.user ? `${entry.user.givenName} ${entry.user.familyName}`.trim() || "No name specified" : "No name specified";
|
|
31943
|
+
studentsById.set(studentId, {
|
|
31944
|
+
studentId,
|
|
31945
|
+
name: name3,
|
|
31946
|
+
email: entry.user?.email || null
|
|
31947
|
+
});
|
|
31948
|
+
}
|
|
31949
|
+
}
|
|
31950
|
+
const students = [...studentsById.values()].toSorted((a, b) => a.name.localeCompare(b.name) || a.studentId.localeCompare(b.studentId));
|
|
31951
|
+
if (selectedStudentId && !studentsById.has(selectedStudentId)) {
|
|
31952
|
+
students.push({
|
|
31953
|
+
studentId: selectedStudentId,
|
|
31954
|
+
name: selectedStudentId,
|
|
31955
|
+
email: null
|
|
31956
|
+
});
|
|
31957
|
+
}
|
|
31958
|
+
const eventResult = await this.fetchCaliperEventsForGame(client, gameSource, {
|
|
31959
|
+
limit: TimebackAdminService.DISCREPANCY_QUEUE_EVENT_FETCH_LIMIT,
|
|
31960
|
+
offset: safeEventOffset,
|
|
31961
|
+
...selectedStudentId ? { studentId: selectedStudentId } : {},
|
|
31962
|
+
...dateRange
|
|
31963
|
+
});
|
|
31964
|
+
const { events, pagination } = eventResult;
|
|
31965
|
+
let effectiveEventPageLimit = pagination.limit;
|
|
31966
|
+
if (effectiveEventPageLimit <= 0) {
|
|
31967
|
+
effectiveEventPageLimit = events.length > 0 ? events.length : TimebackAdminService.DISCREPANCY_QUEUE_EVENT_FETCH_LIMIT;
|
|
31968
|
+
}
|
|
31969
|
+
const relevantCourseIds = new Set([courseId]);
|
|
31970
|
+
const preliminaryActivityGroups = mapCaliperEventsToCompletedStudentGameplayActivities(events, relevantCourseIds, selectedStudentId ? { studentId: selectedStudentId } : {});
|
|
31971
|
+
const preliminaryRunIds = getTimebackActivityRunIds(preliminaryActivityGroups);
|
|
31972
|
+
const verificationsByKey = await this.getMetricDiscrepancyVerificationsByKey({
|
|
31973
|
+
gameId,
|
|
31974
|
+
courseId,
|
|
31975
|
+
runIds: preliminaryRunIds
|
|
31976
|
+
});
|
|
31977
|
+
const queueFilterOptions = {
|
|
31978
|
+
...selectedStudentId ? { studentId: selectedStudentId } : {},
|
|
31979
|
+
discrepancyMetricScopes: options.discrepancyMetricScopes,
|
|
31980
|
+
includeVerified
|
|
31981
|
+
};
|
|
31982
|
+
const preliminaryCandidates = await this.buildMetricDiscrepancyQueueCandidates(user, {
|
|
31983
|
+
gameId,
|
|
31984
|
+
courseId,
|
|
31985
|
+
course: { grade: integration.grade, subject: integration.subject },
|
|
31986
|
+
activityGroups: preliminaryActivityGroups,
|
|
31987
|
+
studentsById,
|
|
31988
|
+
verificationsByKey
|
|
31989
|
+
});
|
|
31990
|
+
const preliminaryItems = selectTimebackMetricDiscrepancyQueueItems(preliminaryCandidates, queueFilterOptions);
|
|
31991
|
+
const runOwnersById = TimebackAdminService.getActivityRunOwners(preliminaryItems.map((item) => ({
|
|
31992
|
+
studentId: item.student.studentId,
|
|
31993
|
+
activities: [item.activity]
|
|
31994
|
+
})));
|
|
31995
|
+
const hydratedRunEvents = await this.hydrateDiscrepancyQueueRunEvents(client, gameSource, {
|
|
31996
|
+
baseEvents: events,
|
|
31997
|
+
runOwnersById
|
|
31998
|
+
});
|
|
31999
|
+
const finalActivityGroups = mapCaliperEventsToCompletedStudentGameplayActivities(hydratedRunEvents.events, relevantCourseIds, {
|
|
32000
|
+
...selectedStudentId ? { studentId: selectedStudentId } : {},
|
|
32001
|
+
runIds: hydratedRunEvents.runIds
|
|
32002
|
+
});
|
|
32003
|
+
const finalCandidates = await this.buildMetricDiscrepancyQueueCandidates(user, {
|
|
32004
|
+
gameId,
|
|
32005
|
+
courseId,
|
|
32006
|
+
course: { grade: integration.grade, subject: integration.subject },
|
|
32007
|
+
activityGroups: finalActivityGroups,
|
|
32008
|
+
studentsById,
|
|
32009
|
+
verificationsByKey
|
|
32010
|
+
});
|
|
32011
|
+
const hydratedItems = selectTimebackMetricDiscrepancyQueueItems(finalCandidates, queueFilterOptions);
|
|
32012
|
+
const items = mergeHydratedMetricDiscrepancyQueueItems({
|
|
32013
|
+
preliminaryItems,
|
|
32014
|
+
hydratedItems,
|
|
32015
|
+
hydratedRunIds: hydratedRunEvents.runIds
|
|
32016
|
+
});
|
|
32017
|
+
const eventPage = {
|
|
32018
|
+
offset: safeEventOffset,
|
|
32019
|
+
limit: effectiveEventPageLimit,
|
|
32020
|
+
total: pagination.total,
|
|
32021
|
+
hasPrevious: safeEventOffset > 0,
|
|
32022
|
+
hasNext: safeEventOffset + effectiveEventPageLimit < pagination.total
|
|
32023
|
+
};
|
|
32024
|
+
return {
|
|
32025
|
+
gameId,
|
|
32026
|
+
courseId,
|
|
32027
|
+
window: window2,
|
|
32028
|
+
dateRange,
|
|
32029
|
+
...selectedStudentId ? { studentId: selectedStudentId } : {},
|
|
32030
|
+
students,
|
|
32031
|
+
discrepancyMetricScopes: [...options.discrepancyMetricScopes ?? []],
|
|
32032
|
+
eventPage,
|
|
32033
|
+
includeVerified,
|
|
32034
|
+
items
|
|
32035
|
+
};
|
|
32036
|
+
}
|
|
32037
|
+
async verifyMetricDiscrepancy(user, options) {
|
|
32038
|
+
const { gameId, courseId, data } = options;
|
|
32039
|
+
const client = this.requireClient();
|
|
32040
|
+
await this.deps.validateDeveloperAccess(user, gameId);
|
|
32041
|
+
const [integration, gameSource] = await Promise.all([
|
|
32042
|
+
this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
32043
|
+
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
32044
|
+
}),
|
|
32045
|
+
this.getGameActivitySource(gameId)
|
|
32046
|
+
]);
|
|
32047
|
+
if (!integration) {
|
|
32048
|
+
throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
|
|
32049
|
+
}
|
|
32050
|
+
await this.assertStudentHasEnrollmentInCourse(client, data.studentId, courseId);
|
|
32051
|
+
const runId = data.runId.toLowerCase();
|
|
32052
|
+
let runEvents;
|
|
32053
|
+
try {
|
|
32054
|
+
runEvents = await this.fetchCaliperEventsForRun(client, gameSource, {
|
|
32055
|
+
runId,
|
|
32056
|
+
studentId: data.studentId
|
|
32057
|
+
});
|
|
32058
|
+
} catch (error) {
|
|
32059
|
+
logger17.warn("Failed to hydrate Caliper events for discrepancy verification", {
|
|
32060
|
+
gameId,
|
|
32061
|
+
courseId,
|
|
32062
|
+
studentId: data.studentId,
|
|
32063
|
+
runId,
|
|
32064
|
+
error: error instanceof Error ? error.message : String(error)
|
|
32065
|
+
});
|
|
32066
|
+
throw new ValidationError("Cannot verify discrepancy until full run data is available");
|
|
32067
|
+
}
|
|
32068
|
+
const activity = mapCaliperEventsToCompletedStudentGameplayActivities(runEvents, new Set([courseId]), {
|
|
32069
|
+
studentId: data.studentId,
|
|
32070
|
+
runIds: new Set([runId])
|
|
32071
|
+
}).flatMap((group) => group.activities).find((runActivity) => runActivity.runId?.toLowerCase() === runId);
|
|
32072
|
+
if (!activity) {
|
|
32073
|
+
throw new ValidationError("Cannot verify discrepancy until full run data is available");
|
|
32074
|
+
}
|
|
32075
|
+
const comparisons = await this.getGameMetricComparisonsForActivities(user, {
|
|
32076
|
+
gameId,
|
|
32077
|
+
studentId: data.studentId,
|
|
32078
|
+
course: { grade: integration.grade, subject: integration.subject },
|
|
32079
|
+
activities: [activity]
|
|
32080
|
+
});
|
|
32081
|
+
const comparison = comparisons.get(activity.runId ?? runId);
|
|
32082
|
+
if (comparison?.status !== "discrepant") {
|
|
32083
|
+
throw new ValidationError("Cannot verify discrepancy because the hydrated run is not discrepant");
|
|
32084
|
+
}
|
|
32085
|
+
const verifiedAt = new Date;
|
|
32086
|
+
const [row] = await this.deps.db.insert(gameTimebackMetricDiscrepancyVerifications).values({
|
|
32087
|
+
gameId,
|
|
32088
|
+
courseId,
|
|
32089
|
+
studentId: data.studentId,
|
|
32090
|
+
runId,
|
|
32091
|
+
activityId: data.activityId ?? null,
|
|
32092
|
+
verifiedByUserId: user.id,
|
|
32093
|
+
verifiedAt
|
|
32094
|
+
}).onConflictDoUpdate({
|
|
32095
|
+
target: [
|
|
32096
|
+
gameTimebackMetricDiscrepancyVerifications.gameId,
|
|
32097
|
+
gameTimebackMetricDiscrepancyVerifications.courseId,
|
|
32098
|
+
gameTimebackMetricDiscrepancyVerifications.studentId,
|
|
32099
|
+
gameTimebackMetricDiscrepancyVerifications.runId
|
|
32100
|
+
],
|
|
32101
|
+
set: {
|
|
32102
|
+
activityId: data.activityId ?? null,
|
|
32103
|
+
verifiedByUserId: user.id,
|
|
32104
|
+
verifiedAt
|
|
32105
|
+
}
|
|
32106
|
+
}).returning();
|
|
32107
|
+
if (!row) {
|
|
32108
|
+
throw new ValidationError("Failed to verify metric discrepancy");
|
|
32109
|
+
}
|
|
32110
|
+
return {
|
|
32111
|
+
status: "ok",
|
|
32112
|
+
verification: TimebackAdminService.mapMetricDiscrepancyVerification(row)
|
|
32113
|
+
};
|
|
32114
|
+
}
|
|
31390
32115
|
async getActivityDetail(user, options) {
|
|
31391
32116
|
const { gameId, studentId, courseId, activityId, runId } = options;
|
|
31392
32117
|
const client = this.requireClient();
|
|
@@ -31401,16 +32126,37 @@ class TimebackAdminService {
|
|
|
31401
32126
|
throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
|
|
31402
32127
|
}
|
|
31403
32128
|
await this.assertStudentHasEnrollmentInCourse(client, studentId, courseId);
|
|
31404
|
-
const events = await this.fetchCaliperEventsForStudent(client, studentId, gameSource, TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
|
|
31405
32129
|
const relevantCourseIds = new Set([courseId]);
|
|
31406
32130
|
let matchedEvents;
|
|
31407
32131
|
let activity;
|
|
31408
32132
|
if (runId) {
|
|
31409
|
-
const
|
|
31410
|
-
|
|
31411
|
-
|
|
31412
|
-
|
|
32133
|
+
const normalizedRunId = TimebackAdminService.normalizeRunIds([runId], 1)[0];
|
|
32134
|
+
if (!normalizedRunId) {
|
|
32135
|
+
matchedEvents = [];
|
|
32136
|
+
activity = null;
|
|
32137
|
+
} else {
|
|
32138
|
+
let runEvents;
|
|
32139
|
+
try {
|
|
32140
|
+
runEvents = await this.fetchCaliperEventsForRun(client, gameSource, {
|
|
32141
|
+
runId: normalizedRunId,
|
|
32142
|
+
studentId
|
|
32143
|
+
});
|
|
32144
|
+
} catch (error) {
|
|
32145
|
+
logger17.warn("Failed to load Caliper events for activity detail run", {
|
|
32146
|
+
runId: normalizedRunId,
|
|
32147
|
+
studentId,
|
|
32148
|
+
activityId,
|
|
32149
|
+
error: error instanceof Error ? error.message : String(error)
|
|
32150
|
+
});
|
|
32151
|
+
runEvents = [];
|
|
32152
|
+
}
|
|
32153
|
+
const gameplayEvents = runEvents.filter((event) => (event.type === "ActivityEvent" || event.type === "TimeSpentEvent") && !isCaliperRemediationOrCompletionEvent(event));
|
|
32154
|
+
const matchingActivityEvents = findCaliperEventGroupContainingExternalId(gameplayEvents, activityId);
|
|
32155
|
+
matchedEvents = matchingActivityEvents ?? [];
|
|
32156
|
+
activity = mapCaliperEventGroupToActivity(matchedEvents, relevantCourseIds);
|
|
32157
|
+
}
|
|
31413
32158
|
} else {
|
|
32159
|
+
const events = await this.fetchCaliperEventsForStudent(client, studentId, gameSource, TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
|
|
31414
32160
|
matchedEvents = events.filter((event) => event.externalId === activityId);
|
|
31415
32161
|
if (matchedEvents.length > 0) {
|
|
31416
32162
|
activity = mapCaliperEventToRemediationActivity(matchedEvents[0], relevantCourseIds) ?? mapCaliperEventGroupToActivity(matchedEvents, relevantCourseIds);
|
|
@@ -31428,9 +32174,22 @@ class TimebackAdminService {
|
|
|
31428
32174
|
activities: [activity]
|
|
31429
32175
|
});
|
|
31430
32176
|
const gameMetricsComparison = activity.runId ? comparisons.get(activity.runId) : undefined;
|
|
32177
|
+
const verificationsByKey = gameMetricsComparison?.status === "discrepant" && activity.runId ? await this.getMetricDiscrepancyVerificationsByKey({
|
|
32178
|
+
gameId,
|
|
32179
|
+
courseId,
|
|
32180
|
+
studentId,
|
|
32181
|
+
runIds: [activity.runId]
|
|
32182
|
+
}) : new Map;
|
|
32183
|
+
const verification2 = getTimebackMetricDiscrepancyVerificationForActivity({
|
|
32184
|
+
studentId,
|
|
32185
|
+
activity,
|
|
32186
|
+
comparison: gameMetricsComparison,
|
|
32187
|
+
verificationsByKey
|
|
32188
|
+
});
|
|
31431
32189
|
const activityWithGameMetrics = gameMetricsComparison ? {
|
|
31432
32190
|
...activity,
|
|
31433
|
-
gameMetricsComparison: summarizeGameRunMetricsComparison(gameMetricsComparison)
|
|
32191
|
+
gameMetricsComparison: summarizeGameRunMetricsComparison(gameMetricsComparison),
|
|
32192
|
+
...verification2 ? { gameMetricsVerification: verification2 } : {}
|
|
31434
32193
|
} : activity;
|
|
31435
32194
|
return {
|
|
31436
32195
|
activity: activityWithGameMetrics,
|
|
@@ -31721,9 +32480,11 @@ var init_timeback_admin_service = __esm(() => {
|
|
|
31721
32480
|
init_types4();
|
|
31722
32481
|
init_utils6();
|
|
31723
32482
|
init_src4();
|
|
32483
|
+
init_timeback3();
|
|
31724
32484
|
init_errors();
|
|
31725
32485
|
init_timeback_admin_metrics_util();
|
|
31726
32486
|
init_timeback_admin_util();
|
|
32487
|
+
init_timeback_discrepancy_queue_util();
|
|
31727
32488
|
init_timeback_game_metrics_comparison_util();
|
|
31728
32489
|
init_timeback_mastery_completion_util();
|
|
31729
32490
|
init_timeback_util();
|
|
@@ -33637,6 +34398,7 @@ function createPlatformServices(deps) {
|
|
|
33637
34398
|
config: config2,
|
|
33638
34399
|
cloudflare: cloudflare2,
|
|
33639
34400
|
storage,
|
|
34401
|
+
r2Storage,
|
|
33640
34402
|
timebackClient,
|
|
33641
34403
|
alerts,
|
|
33642
34404
|
mintPlatformServiceToken,
|
|
@@ -33646,7 +34408,7 @@ function createPlatformServices(deps) {
|
|
|
33646
34408
|
} = deps;
|
|
33647
34409
|
const bucket = new BucketService({
|
|
33648
34410
|
uploadBucket: config2.uploadBucket,
|
|
33649
|
-
storage,
|
|
34411
|
+
storage: r2Storage,
|
|
33650
34412
|
validateDeveloperAccessBySlug,
|
|
33651
34413
|
validateDeveloperAccess
|
|
33652
34414
|
});
|
|
@@ -34295,7 +35057,7 @@ var init_standalone = __esm(() => {
|
|
|
34295
35057
|
// ../api-core/src/services/factory/index.ts
|
|
34296
35058
|
function createServices(ctx) {
|
|
34297
35059
|
const { db: db2, config: config2, providers, cloudflare: cloudflare2, timeback: timeback2, discord } = ctx;
|
|
34298
|
-
const { auth: auth2, storage, cache } = providers;
|
|
35060
|
+
const { auth: auth2, storage, r2Storage, cache } = providers;
|
|
34299
35061
|
const infra2 = createInfraServices({
|
|
34300
35062
|
db: db2,
|
|
34301
35063
|
discord,
|
|
@@ -34318,6 +35080,7 @@ function createServices(ctx) {
|
|
|
34318
35080
|
config: config2,
|
|
34319
35081
|
cloudflare: cloudflare2,
|
|
34320
35082
|
storage,
|
|
35083
|
+
r2Storage,
|
|
34321
35084
|
timebackClient: timeback2,
|
|
34322
35085
|
alerts: infra2.alerts,
|
|
34323
35086
|
mintPlatformServiceToken: auth2.mintPlatformServiceToken.bind(auth2),
|
|
@@ -34996,6 +35759,9 @@ function createCaliperNamespace(client) {
|
|
|
34996
35759
|
if (params.actorEmail) {
|
|
34997
35760
|
query.set("actorEmail", params.actorEmail);
|
|
34998
35761
|
}
|
|
35762
|
+
if (params.sessionId) {
|
|
35763
|
+
query.set("sessionId", params.sessionId);
|
|
35764
|
+
}
|
|
34999
35765
|
if (params.extensions) {
|
|
35000
35766
|
for (const [key, value] of Object.entries(params.extensions)) {
|
|
35001
35767
|
query.set(`extensions.${key}`, value);
|
|
@@ -37364,7 +38130,7 @@ function buildTimebackClient() {
|
|
|
37364
38130
|
}
|
|
37365
38131
|
return;
|
|
37366
38132
|
}
|
|
37367
|
-
var
|
|
38133
|
+
var init_timeback4 = __esm(() => {
|
|
37368
38134
|
init_src2();
|
|
37369
38135
|
init_dist3();
|
|
37370
38136
|
init_config();
|
|
@@ -37372,7 +38138,7 @@ var init_timeback3 = __esm(() => {
|
|
|
37372
38138
|
|
|
37373
38139
|
// src/infrastructure/api/clients/index.ts
|
|
37374
38140
|
var init_clients = __esm(() => {
|
|
37375
|
-
|
|
38141
|
+
init_timeback4();
|
|
37376
38142
|
});
|
|
37377
38143
|
|
|
37378
38144
|
// src/infrastructure/api/providers/auth.provider.ts
|
|
@@ -37563,7 +38329,7 @@ function createSandboxStorageProvider() {
|
|
|
37563
38329
|
files.push({
|
|
37564
38330
|
key,
|
|
37565
38331
|
size: data.body.length,
|
|
37566
|
-
lastModified: new Date,
|
|
38332
|
+
lastModified: new Date().toISOString(),
|
|
37567
38333
|
contentType: data.contentType
|
|
37568
38334
|
});
|
|
37569
38335
|
}
|
|
@@ -37635,9 +38401,11 @@ function buildConfig(options) {
|
|
|
37635
38401
|
});
|
|
37636
38402
|
}
|
|
37637
38403
|
function buildProviders() {
|
|
38404
|
+
const storage2 = createSandboxStorageProvider();
|
|
37638
38405
|
return {
|
|
37639
38406
|
auth: createSandboxAuthProvider(),
|
|
37640
|
-
storage:
|
|
38407
|
+
storage: storage2,
|
|
38408
|
+
r2Storage: storage2,
|
|
37641
38409
|
cache: createSandboxCacheProvider()
|
|
37642
38410
|
};
|
|
37643
38411
|
}
|
|
@@ -92418,7 +93186,7 @@ async function seedTimebackIntegrations(db2, gameId, courses) {
|
|
|
92418
93186
|
}
|
|
92419
93187
|
return seededCount;
|
|
92420
93188
|
}
|
|
92421
|
-
var
|
|
93189
|
+
var init_timeback5 = __esm(() => {
|
|
92422
93190
|
init_tables_index();
|
|
92423
93191
|
init_config();
|
|
92424
93192
|
});
|
|
@@ -92509,7 +93277,7 @@ var init_games = __esm(() => {
|
|
|
92509
93277
|
init_tables_index();
|
|
92510
93278
|
init_constants();
|
|
92511
93279
|
init_logging();
|
|
92512
|
-
|
|
93280
|
+
init_timeback5();
|
|
92513
93281
|
});
|
|
92514
93282
|
|
|
92515
93283
|
// src/database/seed/index.ts
|
|
@@ -92535,7 +93303,7 @@ var init_seed = __esm(() => {
|
|
|
92535
93303
|
init_tables_index();
|
|
92536
93304
|
init_constants();
|
|
92537
93305
|
init_games();
|
|
92538
|
-
|
|
93306
|
+
init_timeback5();
|
|
92539
93307
|
init_games();
|
|
92540
93308
|
});
|
|
92541
93309
|
|
|
@@ -94238,12 +95006,13 @@ var init_session_controller = __esm(() => {
|
|
|
94238
95006
|
});
|
|
94239
95007
|
|
|
94240
95008
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
94241
|
-
var logger45, populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, updateIntegration, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
95009
|
+
var logger45, populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, updateIntegration, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
94242
95010
|
var init_timeback_controller = __esm(() => {
|
|
94243
95011
|
init_esm();
|
|
94244
95012
|
init_schemas_index();
|
|
94245
95013
|
init_src2();
|
|
94246
95014
|
init_src4();
|
|
95015
|
+
init_timeback3();
|
|
94247
95016
|
init_errors();
|
|
94248
95017
|
init_utils11();
|
|
94249
95018
|
logger45 = log.scope("TimebackController");
|
|
@@ -94710,6 +95479,65 @@ var init_timeback_controller = __esm(() => {
|
|
|
94710
95479
|
runId
|
|
94711
95480
|
});
|
|
94712
95481
|
});
|
|
95482
|
+
listMetricDiscrepancies = requireGameManagementAccess(async (ctx) => {
|
|
95483
|
+
const gameId = ctx.params.gameId;
|
|
95484
|
+
const courseId = ctx.params.courseId;
|
|
95485
|
+
const requestedEventOffset = Number(ctx.url.searchParams.get("eventOffset"));
|
|
95486
|
+
const requestedWindow = ctx.url.searchParams.get("window");
|
|
95487
|
+
const startDate = ctx.url.searchParams.get("startDate") ?? undefined;
|
|
95488
|
+
const endDate = ctx.url.searchParams.get("endDate") ?? undefined;
|
|
95489
|
+
const studentId = ctx.url.searchParams.get("studentId")?.trim() || undefined;
|
|
95490
|
+
const discrepancyMetricScopes = parseTimebackDiscrepancyQueueMetrics(ctx.url.searchParams.getAll("discrepancyMetric"));
|
|
95491
|
+
const eventOffset = Number.isFinite(requestedEventOffset) && requestedEventOffset > 0 ? Math.trunc(requestedEventOffset) : 0;
|
|
95492
|
+
const window2 = parseTimebackDiscrepancyQueueWindow(requestedWindow);
|
|
95493
|
+
const includeVerified = ctx.url.searchParams.get("includeVerified") === "true";
|
|
95494
|
+
if (!gameId || !courseId) {
|
|
95495
|
+
throw ApiError.badRequest("Missing gameId or courseId path parameter");
|
|
95496
|
+
}
|
|
95497
|
+
logger45.debug("Listing metric discrepancies", {
|
|
95498
|
+
requesterId: ctx.user.id,
|
|
95499
|
+
gameId,
|
|
95500
|
+
courseId,
|
|
95501
|
+
window: window2,
|
|
95502
|
+
startDate,
|
|
95503
|
+
endDate,
|
|
95504
|
+
studentId,
|
|
95505
|
+
discrepancyMetricScopes,
|
|
95506
|
+
includeVerified,
|
|
95507
|
+
eventOffset
|
|
95508
|
+
});
|
|
95509
|
+
return ctx.services.timebackAdmin.listMetricDiscrepancies(ctx.user, {
|
|
95510
|
+
gameId,
|
|
95511
|
+
courseId,
|
|
95512
|
+
window: window2,
|
|
95513
|
+
startDate,
|
|
95514
|
+
endDate,
|
|
95515
|
+
studentId,
|
|
95516
|
+
discrepancyMetricScopes,
|
|
95517
|
+
includeVerified,
|
|
95518
|
+
eventOffset
|
|
95519
|
+
});
|
|
95520
|
+
});
|
|
95521
|
+
verifyMetricDiscrepancy = requireDeveloper(async (ctx) => {
|
|
95522
|
+
const gameId = ctx.params.gameId;
|
|
95523
|
+
const courseId = ctx.params.courseId;
|
|
95524
|
+
const body2 = await parseRequestBody(ctx.request, VerifyTimebackMetricDiscrepancyRequestSchema);
|
|
95525
|
+
if (!gameId || !courseId) {
|
|
95526
|
+
throw ApiError.badRequest("Missing gameId or courseId path parameter");
|
|
95527
|
+
}
|
|
95528
|
+
logger45.debug("Verifying metric discrepancy", {
|
|
95529
|
+
requesterId: ctx.user.id,
|
|
95530
|
+
gameId,
|
|
95531
|
+
courseId,
|
|
95532
|
+
studentId: body2.studentId,
|
|
95533
|
+
runId: body2.runId
|
|
95534
|
+
});
|
|
95535
|
+
return ctx.services.timebackAdmin.verifyMetricDiscrepancy(ctx.user, {
|
|
95536
|
+
gameId,
|
|
95537
|
+
courseId,
|
|
95538
|
+
data: body2
|
|
95539
|
+
});
|
|
95540
|
+
});
|
|
94713
95541
|
grantXp = requireDeveloper(async (ctx) => {
|
|
94714
95542
|
const body2 = await parseRequestBody(ctx.request, GrantTimebackXpRequestSchema);
|
|
94715
95543
|
logger45.debug("Granting manual XP", {
|
|
@@ -94953,6 +95781,8 @@ var init_timeback_controller = __esm(() => {
|
|
|
94953
95781
|
getGameMetrics,
|
|
94954
95782
|
getStudentActivity,
|
|
94955
95783
|
getActivityDetail,
|
|
95784
|
+
listMetricDiscrepancies,
|
|
95785
|
+
verifyMetricDiscrepancy,
|
|
94956
95786
|
grantXp,
|
|
94957
95787
|
adjustTime,
|
|
94958
95788
|
adjustMastery,
|
|
@@ -95190,7 +96020,7 @@ async function buildMockUserResponse(db2, user, gameId) {
|
|
|
95190
96020
|
timeback: timeback3
|
|
95191
96021
|
};
|
|
95192
96022
|
}
|
|
95193
|
-
var
|
|
96023
|
+
var init_timeback6 = __esm(() => {
|
|
95194
96024
|
init_drizzle_orm();
|
|
95195
96025
|
init_utils11();
|
|
95196
96026
|
init_tables_index();
|
|
@@ -95208,7 +96038,7 @@ var init_users = __esm(() => {
|
|
|
95208
96038
|
init_tables_index();
|
|
95209
96039
|
init_api();
|
|
95210
96040
|
init_error_handler();
|
|
95211
|
-
|
|
96041
|
+
init_timeback6();
|
|
95212
96042
|
usersRouter = new Hono2;
|
|
95213
96043
|
usersRouter.get("/me", async (c2) => {
|
|
95214
96044
|
const user = c2.get("user");
|
|
@@ -95833,15 +96663,16 @@ function hashCode(str) {
|
|
|
95833
96663
|
return Math.abs(hash);
|
|
95834
96664
|
}
|
|
95835
96665
|
var timebackRouter;
|
|
95836
|
-
var
|
|
96666
|
+
var init_timeback7 = __esm(() => {
|
|
95837
96667
|
init_dist4();
|
|
95838
96668
|
init_controllers();
|
|
95839
96669
|
init_errors();
|
|
95840
96670
|
init_utils11();
|
|
95841
96671
|
init_schemas_index();
|
|
96672
|
+
init_timeback3();
|
|
95842
96673
|
init_api();
|
|
95843
96674
|
init_error_handler();
|
|
95844
|
-
|
|
96675
|
+
init_timeback6();
|
|
95845
96676
|
timebackRouter = new Hono2;
|
|
95846
96677
|
timebackRouter.post("/populate-student", async (c2) => c2.json({
|
|
95847
96678
|
status: "no_record"
|
|
@@ -96164,7 +96995,7 @@ var init_routes = __esm(() => {
|
|
|
96164
96995
|
init_users();
|
|
96165
96996
|
init_games2();
|
|
96166
96997
|
init_leaderboard();
|
|
96167
|
-
|
|
96998
|
+
init_timeback7();
|
|
96168
96999
|
init_lti();
|
|
96169
97000
|
});
|
|
96170
97001
|
|