@playcademy/sandbox 0.3.17-beta.20 → 0.3.17-beta.22
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 +125 -15
- package/dist/server.js +125 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1330,7 +1330,7 @@ var package_default;
|
|
|
1330
1330
|
var init_package = __esm(() => {
|
|
1331
1331
|
package_default = {
|
|
1332
1332
|
name: "@playcademy/sandbox",
|
|
1333
|
-
version: "0.3.17-beta.
|
|
1333
|
+
version: "0.3.17-beta.22",
|
|
1334
1334
|
description: "Local development server for Playcademy game development",
|
|
1335
1335
|
type: "module",
|
|
1336
1336
|
exports: {
|
|
@@ -30922,21 +30922,25 @@ class TimebackAdminService {
|
|
|
30922
30922
|
});
|
|
30923
30923
|
return new Map(results);
|
|
30924
30924
|
}
|
|
30925
|
+
async fetchCaliperEventsForStudent(client, studentId, source, limit) {
|
|
30926
|
+
const actorId = `${client.getBaseUrl().replace(/\/$/, "")}/ims/oneroster/rostering/v1p2/users/${studentId}`;
|
|
30927
|
+
const { events } = await client.caliper.events.list({
|
|
30928
|
+
limit,
|
|
30929
|
+
actorId,
|
|
30930
|
+
...source.sourceMode === "production" ? { sensor: source.sensorUrl } : {},
|
|
30931
|
+
extensions: {
|
|
30932
|
+
gameId: source.gameId
|
|
30933
|
+
}
|
|
30934
|
+
});
|
|
30935
|
+
return events;
|
|
30936
|
+
}
|
|
30925
30937
|
async listRecentActivityForStudent(client, studentId, source, relevantCourseIds, maxResults = TimebackAdminService.RECENT_ACTIVITY_LIMIT) {
|
|
30926
30938
|
if (relevantCourseIds.size === 0) {
|
|
30927
30939
|
return [];
|
|
30928
30940
|
}
|
|
30929
30941
|
try {
|
|
30930
|
-
const actorId = `${client.getBaseUrl().replace(/\/$/, "")}/ims/oneroster/rostering/v1p2/users/${studentId}`;
|
|
30931
30942
|
const eventLimit = Math.min(Math.max(200, maxResults * 20), TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
|
|
30932
|
-
const
|
|
30933
|
-
limit: eventLimit,
|
|
30934
|
-
actorId,
|
|
30935
|
-
...source.sourceMode === "production" ? { sensor: source.sensorUrl } : {},
|
|
30936
|
-
extensions: {
|
|
30937
|
-
gameId: source.gameId
|
|
30938
|
-
}
|
|
30939
|
-
});
|
|
30943
|
+
const events = await this.fetchCaliperEventsForStudent(client, studentId, source, eventLimit);
|
|
30940
30944
|
return TimebackAdminService.mapRecentActivityItems(events, relevantCourseIds).slice(0, maxResults);
|
|
30941
30945
|
} catch (error) {
|
|
30942
30946
|
logger16.warn("Failed to load recent Caliper activity", {
|
|
@@ -31060,6 +31064,42 @@ class TimebackAdminService {
|
|
|
31060
31064
|
const hasMore = allActivities.length > safeOffset + safeLimit;
|
|
31061
31065
|
return { activities, hasMore };
|
|
31062
31066
|
}
|
|
31067
|
+
async getActivityDetail(user, options) {
|
|
31068
|
+
const { gameId, studentId, courseId, activityId, runId } = options;
|
|
31069
|
+
const client = this.requireClient();
|
|
31070
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
31071
|
+
const [integration, gameSource] = await Promise.all([
|
|
31072
|
+
this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31073
|
+
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
31074
|
+
}),
|
|
31075
|
+
this.getGameActivitySource(gameId)
|
|
31076
|
+
]);
|
|
31077
|
+
if (!integration) {
|
|
31078
|
+
throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
|
|
31079
|
+
}
|
|
31080
|
+
await this.assertStudentEnrolledInCourse(client, studentId, courseId);
|
|
31081
|
+
const events = await this.fetchCaliperEventsForStudent(client, studentId, gameSource, TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
|
|
31082
|
+
const relevantCourseIds = new Set([courseId]);
|
|
31083
|
+
let matchedEvents;
|
|
31084
|
+
let activity;
|
|
31085
|
+
if (runId) {
|
|
31086
|
+
const gameplayEvents = events.filter((event) => (event.type === "ActivityEvent" || event.type === "TimeSpentEvent") && !isCaliperRemediationOrCompletionEvent(event));
|
|
31087
|
+
const groups = groupCaliperEventsByRun(gameplayEvents);
|
|
31088
|
+
matchedEvents = [...groups.values()].find((group) => group.some((event) => getCanonicalRunId(event.session) === runId && event.externalId === activityId)) ?? [];
|
|
31089
|
+
activity = mapCaliperEventGroupToActivity(matchedEvents, relevantCourseIds);
|
|
31090
|
+
} else {
|
|
31091
|
+
matchedEvents = events.filter((event) => event.externalId === activityId);
|
|
31092
|
+
if (matchedEvents.length > 0) {
|
|
31093
|
+
activity = mapCaliperEventToRemediationActivity(matchedEvents[0], relevantCourseIds) ?? mapCaliperEventGroupToActivity(matchedEvents, relevantCourseIds);
|
|
31094
|
+
} else {
|
|
31095
|
+
activity = null;
|
|
31096
|
+
}
|
|
31097
|
+
}
|
|
31098
|
+
if (!activity) {
|
|
31099
|
+
throw new NotFoundError("Activity", activityId);
|
|
31100
|
+
}
|
|
31101
|
+
return { activity, rawEvents: matchedEvents };
|
|
31102
|
+
}
|
|
31063
31103
|
async grantManualXp(data, user) {
|
|
31064
31104
|
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId);
|
|
31065
31105
|
await client.recordAdminXpAdjustment({
|
|
@@ -36277,7 +36317,7 @@ class MasteryTracker {
|
|
|
36277
36317
|
}
|
|
36278
36318
|
async checkProgress(input) {
|
|
36279
36319
|
const { studentId, courseId, resourceId, masteredUnits } = input;
|
|
36280
|
-
if (typeof masteredUnits !== "number" || masteredUnits
|
|
36320
|
+
if (typeof masteredUnits !== "number" || masteredUnits === 0) {
|
|
36281
36321
|
return;
|
|
36282
36322
|
}
|
|
36283
36323
|
const status = await this.calculateStatus({
|
|
@@ -36289,9 +36329,11 @@ class MasteryTracker {
|
|
|
36289
36329
|
if (!status) {
|
|
36290
36330
|
return;
|
|
36291
36331
|
}
|
|
36332
|
+
const wasComplete = status.historicalMasteredUnits >= status.masterableUnits;
|
|
36292
36333
|
return {
|
|
36293
36334
|
pctCompleteApp: status.pctCompleteApp,
|
|
36294
|
-
masteryAchieved:
|
|
36335
|
+
masteryAchieved: !wasComplete && status.isComplete,
|
|
36336
|
+
masteryRevoked: wasComplete && !status.isComplete
|
|
36295
36337
|
};
|
|
36296
36338
|
}
|
|
36297
36339
|
async getStatus(input) {
|
|
@@ -36332,7 +36374,7 @@ class MasteryTracker {
|
|
|
36332
36374
|
return;
|
|
36333
36375
|
}
|
|
36334
36376
|
const historicalMasteredUnits = this.sumAnalyticsMetric(facts, "masteredUnits");
|
|
36335
|
-
const totalMastered = historicalMasteredUnits + additionalMasteredUnits;
|
|
36377
|
+
const totalMastered = Math.max(0, historicalMasteredUnits + additionalMasteredUnits);
|
|
36336
36378
|
const rawPct = totalMastered / masterableUnits * 100;
|
|
36337
36379
|
const pctCompleteApp = Math.min(100, Math.max(0, Math.round(rawPct)));
|
|
36338
36380
|
return {
|
|
@@ -36382,6 +36424,45 @@ class MasteryTracker {
|
|
|
36382
36424
|
});
|
|
36383
36425
|
}
|
|
36384
36426
|
}
|
|
36427
|
+
async revokeCompletionEntry(studentId, courseId, classId, appName) {
|
|
36428
|
+
const ids = deriveSourcedIds2(courseId);
|
|
36429
|
+
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
36430
|
+
const resultId = `${lineItemId}:${studentId}:completion`;
|
|
36431
|
+
try {
|
|
36432
|
+
await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
|
|
36433
|
+
sourcedId: lineItemId,
|
|
36434
|
+
title: "Mastery Completion",
|
|
36435
|
+
status: ONEROSTER_STATUS4.active,
|
|
36436
|
+
...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } },
|
|
36437
|
+
...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
|
|
36438
|
+
});
|
|
36439
|
+
await this.onerosterNamespace.assessmentResults.upsert(resultId, {
|
|
36440
|
+
sourcedId: resultId,
|
|
36441
|
+
status: ONEROSTER_STATUS4.active,
|
|
36442
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
36443
|
+
student: { sourcedId: studentId },
|
|
36444
|
+
score: 0,
|
|
36445
|
+
scoreDate: new Date().toISOString(),
|
|
36446
|
+
scoreStatus: SCORE_STATUS4.notSubmitted,
|
|
36447
|
+
inProgress: "true",
|
|
36448
|
+
metadata: {
|
|
36449
|
+
isMasteryCompletion: true,
|
|
36450
|
+
appName
|
|
36451
|
+
}
|
|
36452
|
+
});
|
|
36453
|
+
log.info("[MasteryTracker] Revoked mastery completion entry", {
|
|
36454
|
+
studentId,
|
|
36455
|
+
lineItemId,
|
|
36456
|
+
resultId
|
|
36457
|
+
});
|
|
36458
|
+
} catch (error) {
|
|
36459
|
+
log.error("[MasteryTracker] Failed to revoke mastery completion entry", {
|
|
36460
|
+
studentId,
|
|
36461
|
+
lineItemId,
|
|
36462
|
+
error
|
|
36463
|
+
});
|
|
36464
|
+
}
|
|
36465
|
+
}
|
|
36385
36466
|
async resolveMasterableUnits(resourceId) {
|
|
36386
36467
|
if (!resourceId) {
|
|
36387
36468
|
return;
|
|
@@ -36591,6 +36672,9 @@ class ProgressRecorder {
|
|
|
36591
36672
|
sensorUrl: progressData.sensorUrl
|
|
36592
36673
|
});
|
|
36593
36674
|
}
|
|
36675
|
+
if (masteryProgress?.masteryRevoked) {
|
|
36676
|
+
await this.masteryTracker.revokeCompletionEntry(studentId, courseId, progressData.classId, progressData.appName);
|
|
36677
|
+
}
|
|
36594
36678
|
await this.emitCaliperEvent({
|
|
36595
36679
|
studentId,
|
|
36596
36680
|
studentEmail,
|
|
@@ -94183,7 +94267,7 @@ var init_schemas11 = __esm(() => {
|
|
|
94183
94267
|
inactiveSeconds: exports_external.number().nonnegative().optional()
|
|
94184
94268
|
}).optional(),
|
|
94185
94269
|
xpEarned: exports_external.number().optional(),
|
|
94186
|
-
masteredUnits: exports_external.number().
|
|
94270
|
+
masteredUnits: exports_external.number().optional(),
|
|
94187
94271
|
extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
94188
94272
|
});
|
|
94189
94273
|
AdvanceCourseRequestSchema = exports_external.object({
|
|
@@ -96478,7 +96562,7 @@ var init_sprite_controller = __esm(() => {
|
|
|
96478
96562
|
});
|
|
96479
96563
|
|
|
96480
96564
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
96481
|
-
var logger64, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getRoster, getStudentOverview, getStudentActivity, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, timeback2;
|
|
96565
|
+
var logger64, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getRoster, getStudentOverview, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, timeback2;
|
|
96482
96566
|
var init_timeback_controller = __esm(() => {
|
|
96483
96567
|
init_esm();
|
|
96484
96568
|
init_schemas_index();
|
|
@@ -96814,6 +96898,31 @@ var init_timeback_controller = __esm(() => {
|
|
|
96814
96898
|
offset
|
|
96815
96899
|
});
|
|
96816
96900
|
});
|
|
96901
|
+
getActivityDetail = requireGameManagementAccess(async (ctx) => {
|
|
96902
|
+
const timebackId = ctx.params.timebackId;
|
|
96903
|
+
const courseId = ctx.params.courseId;
|
|
96904
|
+
const activityId = ctx.params.activityId;
|
|
96905
|
+
const gameId = ctx.url.searchParams.get("gameId") || undefined;
|
|
96906
|
+
const runId = ctx.url.searchParams.get("runId") || undefined;
|
|
96907
|
+
if (!timebackId || !courseId || !activityId || !gameId) {
|
|
96908
|
+
throw ApiError.badRequest("Missing timebackId, courseId, or activityId path parameter, or gameId query parameter");
|
|
96909
|
+
}
|
|
96910
|
+
logger64.debug("Getting activity detail", {
|
|
96911
|
+
requesterId: ctx.user.id,
|
|
96912
|
+
timebackId,
|
|
96913
|
+
courseId,
|
|
96914
|
+
activityId,
|
|
96915
|
+
gameId,
|
|
96916
|
+
runId
|
|
96917
|
+
});
|
|
96918
|
+
return ctx.services.timebackAdmin.getActivityDetail(ctx.user, {
|
|
96919
|
+
gameId,
|
|
96920
|
+
studentId: timebackId,
|
|
96921
|
+
courseId,
|
|
96922
|
+
activityId,
|
|
96923
|
+
runId
|
|
96924
|
+
});
|
|
96925
|
+
});
|
|
96817
96926
|
grantXp = requireDeveloper(async (ctx) => {
|
|
96818
96927
|
const body2 = await parseRequestBody(ctx.request, GrantTimebackXpRequestSchema);
|
|
96819
96928
|
logger64.debug("Granting manual XP", {
|
|
@@ -96916,6 +97025,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
96916
97025
|
getRoster,
|
|
96917
97026
|
getStudentOverview,
|
|
96918
97027
|
getStudentActivity,
|
|
97028
|
+
getActivityDetail,
|
|
96919
97029
|
grantXp,
|
|
96920
97030
|
adjustTime,
|
|
96921
97031
|
adjustMastery,
|
package/dist/server.js
CHANGED
|
@@ -1329,7 +1329,7 @@ var package_default;
|
|
|
1329
1329
|
var init_package = __esm(() => {
|
|
1330
1330
|
package_default = {
|
|
1331
1331
|
name: "@playcademy/sandbox",
|
|
1332
|
-
version: "0.3.17-beta.
|
|
1332
|
+
version: "0.3.17-beta.22",
|
|
1333
1333
|
description: "Local development server for Playcademy game development",
|
|
1334
1334
|
type: "module",
|
|
1335
1335
|
exports: {
|
|
@@ -30921,21 +30921,25 @@ class TimebackAdminService {
|
|
|
30921
30921
|
});
|
|
30922
30922
|
return new Map(results);
|
|
30923
30923
|
}
|
|
30924
|
+
async fetchCaliperEventsForStudent(client, studentId, source, limit) {
|
|
30925
|
+
const actorId = `${client.getBaseUrl().replace(/\/$/, "")}/ims/oneroster/rostering/v1p2/users/${studentId}`;
|
|
30926
|
+
const { events } = await client.caliper.events.list({
|
|
30927
|
+
limit,
|
|
30928
|
+
actorId,
|
|
30929
|
+
...source.sourceMode === "production" ? { sensor: source.sensorUrl } : {},
|
|
30930
|
+
extensions: {
|
|
30931
|
+
gameId: source.gameId
|
|
30932
|
+
}
|
|
30933
|
+
});
|
|
30934
|
+
return events;
|
|
30935
|
+
}
|
|
30924
30936
|
async listRecentActivityForStudent(client, studentId, source, relevantCourseIds, maxResults = TimebackAdminService.RECENT_ACTIVITY_LIMIT) {
|
|
30925
30937
|
if (relevantCourseIds.size === 0) {
|
|
30926
30938
|
return [];
|
|
30927
30939
|
}
|
|
30928
30940
|
try {
|
|
30929
|
-
const actorId = `${client.getBaseUrl().replace(/\/$/, "")}/ims/oneroster/rostering/v1p2/users/${studentId}`;
|
|
30930
30941
|
const eventLimit = Math.min(Math.max(200, maxResults * 20), TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
|
|
30931
|
-
const
|
|
30932
|
-
limit: eventLimit,
|
|
30933
|
-
actorId,
|
|
30934
|
-
...source.sourceMode === "production" ? { sensor: source.sensorUrl } : {},
|
|
30935
|
-
extensions: {
|
|
30936
|
-
gameId: source.gameId
|
|
30937
|
-
}
|
|
30938
|
-
});
|
|
30942
|
+
const events = await this.fetchCaliperEventsForStudent(client, studentId, source, eventLimit);
|
|
30939
30943
|
return TimebackAdminService.mapRecentActivityItems(events, relevantCourseIds).slice(0, maxResults);
|
|
30940
30944
|
} catch (error) {
|
|
30941
30945
|
logger16.warn("Failed to load recent Caliper activity", {
|
|
@@ -31059,6 +31063,42 @@ class TimebackAdminService {
|
|
|
31059
31063
|
const hasMore = allActivities.length > safeOffset + safeLimit;
|
|
31060
31064
|
return { activities, hasMore };
|
|
31061
31065
|
}
|
|
31066
|
+
async getActivityDetail(user, options) {
|
|
31067
|
+
const { gameId, studentId, courseId, activityId, runId } = options;
|
|
31068
|
+
const client = this.requireClient();
|
|
31069
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
31070
|
+
const [integration, gameSource] = await Promise.all([
|
|
31071
|
+
this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31072
|
+
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
31073
|
+
}),
|
|
31074
|
+
this.getGameActivitySource(gameId)
|
|
31075
|
+
]);
|
|
31076
|
+
if (!integration) {
|
|
31077
|
+
throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
|
|
31078
|
+
}
|
|
31079
|
+
await this.assertStudentEnrolledInCourse(client, studentId, courseId);
|
|
31080
|
+
const events = await this.fetchCaliperEventsForStudent(client, studentId, gameSource, TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
|
|
31081
|
+
const relevantCourseIds = new Set([courseId]);
|
|
31082
|
+
let matchedEvents;
|
|
31083
|
+
let activity;
|
|
31084
|
+
if (runId) {
|
|
31085
|
+
const gameplayEvents = events.filter((event) => (event.type === "ActivityEvent" || event.type === "TimeSpentEvent") && !isCaliperRemediationOrCompletionEvent(event));
|
|
31086
|
+
const groups = groupCaliperEventsByRun(gameplayEvents);
|
|
31087
|
+
matchedEvents = [...groups.values()].find((group) => group.some((event) => getCanonicalRunId(event.session) === runId && event.externalId === activityId)) ?? [];
|
|
31088
|
+
activity = mapCaliperEventGroupToActivity(matchedEvents, relevantCourseIds);
|
|
31089
|
+
} else {
|
|
31090
|
+
matchedEvents = events.filter((event) => event.externalId === activityId);
|
|
31091
|
+
if (matchedEvents.length > 0) {
|
|
31092
|
+
activity = mapCaliperEventToRemediationActivity(matchedEvents[0], relevantCourseIds) ?? mapCaliperEventGroupToActivity(matchedEvents, relevantCourseIds);
|
|
31093
|
+
} else {
|
|
31094
|
+
activity = null;
|
|
31095
|
+
}
|
|
31096
|
+
}
|
|
31097
|
+
if (!activity) {
|
|
31098
|
+
throw new NotFoundError("Activity", activityId);
|
|
31099
|
+
}
|
|
31100
|
+
return { activity, rawEvents: matchedEvents };
|
|
31101
|
+
}
|
|
31062
31102
|
async grantManualXp(data, user) {
|
|
31063
31103
|
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId);
|
|
31064
31104
|
await client.recordAdminXpAdjustment({
|
|
@@ -36276,7 +36316,7 @@ class MasteryTracker {
|
|
|
36276
36316
|
}
|
|
36277
36317
|
async checkProgress(input) {
|
|
36278
36318
|
const { studentId, courseId, resourceId, masteredUnits } = input;
|
|
36279
|
-
if (typeof masteredUnits !== "number" || masteredUnits
|
|
36319
|
+
if (typeof masteredUnits !== "number" || masteredUnits === 0) {
|
|
36280
36320
|
return;
|
|
36281
36321
|
}
|
|
36282
36322
|
const status = await this.calculateStatus({
|
|
@@ -36288,9 +36328,11 @@ class MasteryTracker {
|
|
|
36288
36328
|
if (!status) {
|
|
36289
36329
|
return;
|
|
36290
36330
|
}
|
|
36331
|
+
const wasComplete = status.historicalMasteredUnits >= status.masterableUnits;
|
|
36291
36332
|
return {
|
|
36292
36333
|
pctCompleteApp: status.pctCompleteApp,
|
|
36293
|
-
masteryAchieved:
|
|
36334
|
+
masteryAchieved: !wasComplete && status.isComplete,
|
|
36335
|
+
masteryRevoked: wasComplete && !status.isComplete
|
|
36294
36336
|
};
|
|
36295
36337
|
}
|
|
36296
36338
|
async getStatus(input) {
|
|
@@ -36331,7 +36373,7 @@ class MasteryTracker {
|
|
|
36331
36373
|
return;
|
|
36332
36374
|
}
|
|
36333
36375
|
const historicalMasteredUnits = this.sumAnalyticsMetric(facts, "masteredUnits");
|
|
36334
|
-
const totalMastered = historicalMasteredUnits + additionalMasteredUnits;
|
|
36376
|
+
const totalMastered = Math.max(0, historicalMasteredUnits + additionalMasteredUnits);
|
|
36335
36377
|
const rawPct = totalMastered / masterableUnits * 100;
|
|
36336
36378
|
const pctCompleteApp = Math.min(100, Math.max(0, Math.round(rawPct)));
|
|
36337
36379
|
return {
|
|
@@ -36381,6 +36423,45 @@ class MasteryTracker {
|
|
|
36381
36423
|
});
|
|
36382
36424
|
}
|
|
36383
36425
|
}
|
|
36426
|
+
async revokeCompletionEntry(studentId, courseId, classId, appName) {
|
|
36427
|
+
const ids = deriveSourcedIds2(courseId);
|
|
36428
|
+
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
36429
|
+
const resultId = `${lineItemId}:${studentId}:completion`;
|
|
36430
|
+
try {
|
|
36431
|
+
await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
|
|
36432
|
+
sourcedId: lineItemId,
|
|
36433
|
+
title: "Mastery Completion",
|
|
36434
|
+
status: ONEROSTER_STATUS4.active,
|
|
36435
|
+
...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } },
|
|
36436
|
+
...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
|
|
36437
|
+
});
|
|
36438
|
+
await this.onerosterNamespace.assessmentResults.upsert(resultId, {
|
|
36439
|
+
sourcedId: resultId,
|
|
36440
|
+
status: ONEROSTER_STATUS4.active,
|
|
36441
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
36442
|
+
student: { sourcedId: studentId },
|
|
36443
|
+
score: 0,
|
|
36444
|
+
scoreDate: new Date().toISOString(),
|
|
36445
|
+
scoreStatus: SCORE_STATUS4.notSubmitted,
|
|
36446
|
+
inProgress: "true",
|
|
36447
|
+
metadata: {
|
|
36448
|
+
isMasteryCompletion: true,
|
|
36449
|
+
appName
|
|
36450
|
+
}
|
|
36451
|
+
});
|
|
36452
|
+
log.info("[MasteryTracker] Revoked mastery completion entry", {
|
|
36453
|
+
studentId,
|
|
36454
|
+
lineItemId,
|
|
36455
|
+
resultId
|
|
36456
|
+
});
|
|
36457
|
+
} catch (error) {
|
|
36458
|
+
log.error("[MasteryTracker] Failed to revoke mastery completion entry", {
|
|
36459
|
+
studentId,
|
|
36460
|
+
lineItemId,
|
|
36461
|
+
error
|
|
36462
|
+
});
|
|
36463
|
+
}
|
|
36464
|
+
}
|
|
36384
36465
|
async resolveMasterableUnits(resourceId) {
|
|
36385
36466
|
if (!resourceId) {
|
|
36386
36467
|
return;
|
|
@@ -36590,6 +36671,9 @@ class ProgressRecorder {
|
|
|
36590
36671
|
sensorUrl: progressData.sensorUrl
|
|
36591
36672
|
});
|
|
36592
36673
|
}
|
|
36674
|
+
if (masteryProgress?.masteryRevoked) {
|
|
36675
|
+
await this.masteryTracker.revokeCompletionEntry(studentId, courseId, progressData.classId, progressData.appName);
|
|
36676
|
+
}
|
|
36593
36677
|
await this.emitCaliperEvent({
|
|
36594
36678
|
studentId,
|
|
36595
36679
|
studentEmail,
|
|
@@ -94182,7 +94266,7 @@ var init_schemas11 = __esm(() => {
|
|
|
94182
94266
|
inactiveSeconds: exports_external.number().nonnegative().optional()
|
|
94183
94267
|
}).optional(),
|
|
94184
94268
|
xpEarned: exports_external.number().optional(),
|
|
94185
|
-
masteredUnits: exports_external.number().
|
|
94269
|
+
masteredUnits: exports_external.number().optional(),
|
|
94186
94270
|
extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
94187
94271
|
});
|
|
94188
94272
|
AdvanceCourseRequestSchema = exports_external.object({
|
|
@@ -96477,7 +96561,7 @@ var init_sprite_controller = __esm(() => {
|
|
|
96477
96561
|
});
|
|
96478
96562
|
|
|
96479
96563
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
96480
|
-
var logger64, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getRoster, getStudentOverview, getStudentActivity, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, timeback2;
|
|
96564
|
+
var logger64, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getRoster, getStudentOverview, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, timeback2;
|
|
96481
96565
|
var init_timeback_controller = __esm(() => {
|
|
96482
96566
|
init_esm();
|
|
96483
96567
|
init_schemas_index();
|
|
@@ -96813,6 +96897,31 @@ var init_timeback_controller = __esm(() => {
|
|
|
96813
96897
|
offset
|
|
96814
96898
|
});
|
|
96815
96899
|
});
|
|
96900
|
+
getActivityDetail = requireGameManagementAccess(async (ctx) => {
|
|
96901
|
+
const timebackId = ctx.params.timebackId;
|
|
96902
|
+
const courseId = ctx.params.courseId;
|
|
96903
|
+
const activityId = ctx.params.activityId;
|
|
96904
|
+
const gameId = ctx.url.searchParams.get("gameId") || undefined;
|
|
96905
|
+
const runId = ctx.url.searchParams.get("runId") || undefined;
|
|
96906
|
+
if (!timebackId || !courseId || !activityId || !gameId) {
|
|
96907
|
+
throw ApiError.badRequest("Missing timebackId, courseId, or activityId path parameter, or gameId query parameter");
|
|
96908
|
+
}
|
|
96909
|
+
logger64.debug("Getting activity detail", {
|
|
96910
|
+
requesterId: ctx.user.id,
|
|
96911
|
+
timebackId,
|
|
96912
|
+
courseId,
|
|
96913
|
+
activityId,
|
|
96914
|
+
gameId,
|
|
96915
|
+
runId
|
|
96916
|
+
});
|
|
96917
|
+
return ctx.services.timebackAdmin.getActivityDetail(ctx.user, {
|
|
96918
|
+
gameId,
|
|
96919
|
+
studentId: timebackId,
|
|
96920
|
+
courseId,
|
|
96921
|
+
activityId,
|
|
96922
|
+
runId
|
|
96923
|
+
});
|
|
96924
|
+
});
|
|
96816
96925
|
grantXp = requireDeveloper(async (ctx) => {
|
|
96817
96926
|
const body2 = await parseRequestBody(ctx.request, GrantTimebackXpRequestSchema);
|
|
96818
96927
|
logger64.debug("Granting manual XP", {
|
|
@@ -96915,6 +97024,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
96915
97024
|
getRoster,
|
|
96916
97025
|
getStudentOverview,
|
|
96917
97026
|
getStudentActivity,
|
|
97027
|
+
getActivityDetail,
|
|
96918
97028
|
grantXp,
|
|
96919
97029
|
adjustTime,
|
|
96920
97030
|
adjustMastery,
|