@happyvertical/smrt-education 0.57.41 → 0.57.43

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.
Files changed (38) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +10 -0
  3. package/agents/group-sharing.md +278 -0
  4. package/agents/submission-service.md +10 -0
  5. package/dist/chunks/{workbook-photo-service-D5DUgh-C.js → workbook-photo-service-C6mbUUp6.js} +196 -65
  6. package/dist/chunks/workbook-photo-service-C6mbUUp6.js.map +1 -0
  7. package/dist/group-sharing-authorization.d.ts +67 -0
  8. package/dist/group-sharing-authorization.d.ts.map +1 -0
  9. package/dist/group-sharing-comments.d.ts +30 -0
  10. package/dist/group-sharing-comments.d.ts.map +1 -0
  11. package/dist/group-sharing-contracts.d.ts +201 -0
  12. package/dist/group-sharing-contracts.d.ts.map +1 -0
  13. package/dist/group-sharing-hash.d.ts +3 -0
  14. package/dist/group-sharing-hash.d.ts.map +1 -0
  15. package/dist/group-sharing-models.d.ts +106 -0
  16. package/dist/group-sharing-models.d.ts.map +1 -0
  17. package/dist/group-sharing-policy.d.ts +19 -0
  18. package/dist/group-sharing-policy.d.ts.map +1 -0
  19. package/dist/group-sharing-reader.d.ts +45 -0
  20. package/dist/group-sharing-reader.d.ts.map +1 -0
  21. package/dist/group-sharing-service.d.ts +108 -0
  22. package/dist/group-sharing-service.d.ts.map +1 -0
  23. package/dist/group-sharing-workspace.d.ts +10 -0
  24. package/dist/group-sharing-workspace.d.ts.map +1 -0
  25. package/dist/index.d.ts +8 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2032 -69
  28. package/dist/index.js.map +1 -1
  29. package/dist/ingestion.js +1 -1
  30. package/dist/manifest.json +2728 -1
  31. package/dist/recipes.d.ts +24 -0
  32. package/dist/recipes.d.ts.map +1 -1
  33. package/dist/smrt-knowledge.json +1073 -12
  34. package/dist/submission-service.d.ts +2 -0
  35. package/dist/submission-service.d.ts.map +1 -1
  36. package/dist/workbook-service.d.ts.map +1 -1
  37. package/package.json +9 -9
  38. package/dist/chunks/workbook-photo-service-D5DUgh-C.js.map +0 -1
@@ -1216,6 +1216,160 @@ async function familyAdoptionOwner(db, photo) {
1216
1216
  return String(family.owner_profile_id);
1217
1217
  }
1218
1218
  //#endregion
1219
+ //#region src/group-sharing-contracts.ts
1220
+ var GroupSharingAuthorizationError = class extends Error {
1221
+ constructor(message = "Group sharing authorization denied") {
1222
+ super(message);
1223
+ this.name = "GroupSharingAuthorizationError";
1224
+ }
1225
+ };
1226
+ var GroupSharingConflictError = class extends Error {
1227
+ constructor(message = "Group sharing state conflict") {
1228
+ super(message);
1229
+ this.name = "GroupSharingConflictError";
1230
+ }
1231
+ };
1232
+ var GroupSharingPolicyValidationError = class extends Error {
1233
+ constructor(message = "Invalid group sharing policy") {
1234
+ super(message);
1235
+ this.name = "GroupSharingPolicyValidationError";
1236
+ }
1237
+ };
1238
+ function record$1(value) {
1239
+ return !!value && typeof value === "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
1240
+ }
1241
+ function keys(value, expected) {
1242
+ const actual = Object.keys(value);
1243
+ return actual.length === expected.length && actual.every((key) => expected.includes(key));
1244
+ }
1245
+ function selector(value) {
1246
+ if (!record$1(value)) return false;
1247
+ if (value.mode === "all") return keys(value, ["mode"]);
1248
+ return value.mode === "include" && keys(value, ["mode", "values"]) && Array.isArray(value.values) && value.values.length <= 1e3 && value.values.every((v) => typeof v === "string" && v.length > 0 && v.length <= 256 && v.trim() === v) && new Set(value.values).size === value.values.length;
1249
+ }
1250
+ function validateGroupSharingPolicy(value) {
1251
+ const booleans = [
1252
+ "shareWork",
1253
+ "shareMedia",
1254
+ "shareFeedback",
1255
+ "shareProgress",
1256
+ "parentReviewedOnly"
1257
+ ];
1258
+ if (!record$1(value) || !keys(value, [
1259
+ "schemaVersion",
1260
+ ...booleans,
1261
+ "commentVisibility",
1262
+ "historyFrom",
1263
+ "subjects",
1264
+ "courseIds"
1265
+ ]) || value.schemaVersion !== 1 || !["parent-only", "learner-visible"].includes(value.commentVisibility) || !booleans.every((key) => typeof value[key] === "boolean") || typeof value.historyFrom !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value.historyFrom) || !Number.isFinite(Date.parse(value.historyFrom)) || new Date(value.historyFrom).toISOString() !== value.historyFrom || !selector(value.subjects) || !selector(value.courseIds)) throw new GroupSharingPolicyValidationError();
1266
+ }
1267
+ function canonicalGroupSharingPolicy(value) {
1268
+ validateGroupSharingPolicy(value);
1269
+ const ordered = (s) => s.mode === "all" ? { mode: "all" } : {
1270
+ mode: "include",
1271
+ values: [...s.values].sort()
1272
+ };
1273
+ return JSON.stringify({
1274
+ schemaVersion: 1,
1275
+ shareWork: value.shareWork,
1276
+ shareMedia: value.shareMedia,
1277
+ shareFeedback: value.shareFeedback,
1278
+ shareProgress: value.shareProgress,
1279
+ parentReviewedOnly: value.parentReviewedOnly,
1280
+ commentVisibility: value.commentVisibility,
1281
+ historyFrom: value.historyFrom,
1282
+ subjects: ordered(value.subjects),
1283
+ courseIds: ordered(value.courseIds)
1284
+ });
1285
+ }
1286
+ //#endregion
1287
+ //#region src/group-sharing-policy.ts
1288
+ function evaluateGroupSharingPolicy(policy, facts) {
1289
+ validateGroupSharingPolicy(policy);
1290
+ const reasons = [];
1291
+ const captured = Date.parse(facts.capturedAt);
1292
+ if (!Number.isFinite(captured) || !Number.isFinite(Date.parse(policy.historyFrom)) || captured < Date.parse(policy.historyFrom)) reasons.push("history");
1293
+ if (policy.courseIds.mode === "include" && (!facts.courseId || !policy.courseIds.values.includes(facts.courseId))) reasons.push("course");
1294
+ if (policy.subjects.mode === "include" && (!facts.subject || !policy.subjects.values.includes(facts.subject))) reasons.push("subject");
1295
+ if (policy.parentReviewedOnly && !facts.parentReviewed) reasons.push("parent-review");
1296
+ const eligible = reasons.length === 0;
1297
+ return {
1298
+ eligible,
1299
+ work: eligible && policy.shareWork,
1300
+ media: eligible && policy.shareWork && policy.shareMedia,
1301
+ feedback: eligible && policy.shareFeedback,
1302
+ progress: eligible && policy.shareProgress,
1303
+ reasons
1304
+ };
1305
+ }
1306
+ function summarizeWorkbookEvidence(evidence) {
1307
+ const summaries = /* @__PURE__ */ new Map();
1308
+ const independentSeen = /* @__PURE__ */ new Set();
1309
+ for (const { attempt, revisionId, assessment, confirmed } of evidence) for (const q of assessment.questions) for (const conceptId of q.conceptIds) {
1310
+ const key = `${assessment.vocabulary.namespace}:${assessment.vocabulary.version}:${conceptId}`;
1311
+ const concept = assessment.vocabulary.concepts.find((c) => c.id === conceptId);
1312
+ if (!concept) throw new Error("Unknown workbook concept");
1313
+ let summary = summaries.get(key);
1314
+ if (!summary) {
1315
+ summary = {
1316
+ namespace: assessment.vocabulary.namespace,
1317
+ version: assessment.vocabulary.version,
1318
+ conceptId,
1319
+ label: concept.label,
1320
+ confirmed: {
1321
+ independent: 0,
1322
+ assisted: 0,
1323
+ unknown: 0,
1324
+ correct: 0,
1325
+ needsCorrection: 0,
1326
+ unassessed: 0
1327
+ },
1328
+ provisional: 0,
1329
+ episodes: [],
1330
+ revisionIds: [],
1331
+ cues: [],
1332
+ observations: []
1333
+ };
1334
+ summaries.set(key, summary);
1335
+ }
1336
+ if (!summary.episodes.includes(attempt.episodeId)) summary.episodes.push(attempt.episodeId);
1337
+ if (!summary.revisionIds.includes(revisionId)) summary.revisionIds.push(revisionId);
1338
+ const assistance = attempt.previousAttemptId && attempt.assistance === "independent" ? "unknown" : attempt.assistance;
1339
+ summary.observations.push({
1340
+ submissionId: attempt.submissionId,
1341
+ revisionId,
1342
+ questionId: q.id,
1343
+ episodeId: attempt.episodeId,
1344
+ result: q.result,
1345
+ assistance,
1346
+ isCorrection: Boolean(attempt.previousAttemptId),
1347
+ capturedAt: attempt.capturedAt,
1348
+ confirmed
1349
+ });
1350
+ if (!confirmed) {
1351
+ summary.provisional++;
1352
+ continue;
1353
+ }
1354
+ if (q.result === "unassessed") {
1355
+ summary.confirmed.unassessed++;
1356
+ continue;
1357
+ }
1358
+ const episodeQuestion = `${key}:${attempt.episodeId}:${q.id}`;
1359
+ if (assistance !== "independent" || !independentSeen.has(episodeQuestion)) summary.confirmed[assistance]++;
1360
+ if (assistance === "independent") independentSeen.add(episodeQuestion);
1361
+ if (q.result === "correct") summary.confirmed.correct++;
1362
+ else summary.confirmed.needsCorrection++;
1363
+ if (attempt.previousAttemptId && q.result === "correct" && !summary.cues.includes("improving on this item")) summary.cues.push("improving on this item");
1364
+ }
1365
+ for (const summary of summaries.values()) {
1366
+ if (!summary.confirmed.independent) summary.cues.push("needs a fresh independent check");
1367
+ if (summary.confirmed.needsCorrection) summary.cues.push("needs practice");
1368
+ if (!summary.confirmed.correct && !summary.confirmed.needsCorrection) summary.cues.push("not enough evidence");
1369
+ }
1370
+ return [...summaries.values()];
1371
+ }
1372
+ //#endregion
1219
1373
  //#region src/workbook-contracts.ts
1220
1374
  var WorkbookAssessmentValidationError = class extends Error {
1221
1375
  constructor(message, reason = "invalid_assessment") {
@@ -25641,6 +25795,38 @@ var SubmissionService = class SubmissionService extends LearnerService {
25641
25795
  async getSubmission(request) {
25642
25796
  return this.view((await this.requireSubmission(request, "education.learner.read")).row, request.actorUserId);
25643
25797
  }
25798
+ /** Latest retained completed-work evidence; lesson completion is independent. */
25799
+ async getLatestLessonSubmission(request) {
25800
+ const enrollment = await this.requireEnrollment(request, "education.learner.read");
25801
+ if (!(await this.catalog(request, enrollment.courseId)).sections.some((section) => section.lessons.some(({ lesson }) => lesson.id === request.lessonId))) throw new LearnerAuthorizationError();
25802
+ const progress = await (await LessonProgressCollection.create({ db: this.db })).get({
25803
+ enrollmentId: request.enrollmentId,
25804
+ learnerId: request.learnerId,
25805
+ lessonId: request.lessonId,
25806
+ tenantId: request.tenantId
25807
+ }, { cache: false });
25808
+ if (!progress) return null;
25809
+ const [row] = await (await SubmissionCollection.create({ db: this.db })).list({
25810
+ where: {
25811
+ tenantId: request.tenantId,
25812
+ learnerId: request.learnerId,
25813
+ lessonId: request.lessonId,
25814
+ progressId: String(progress.id),
25815
+ kind: "completed_work",
25816
+ "status in": [
25817
+ "captured",
25818
+ "needs_review",
25819
+ "graded"
25820
+ ]
25821
+ },
25822
+ orderBy: ["submittedAt DESC", "id DESC"],
25823
+ limit: 1
25824
+ });
25825
+ return row ? this.getSubmission({
25826
+ ...request,
25827
+ submissionId: String(row.id)
25828
+ }) : null;
25829
+ }
25644
25830
  async grade(request, row, progress, score, feedback) {
25645
25831
  if (typeof feedback !== "string") throw new Error("Feedback must be text");
25646
25832
  if (score !== null && (typeof score !== "number" || !Number.isFinite(score) || score < 0 || score > 1)) throw new Error("Score must be null or a number from zero to one");
@@ -27341,8 +27527,7 @@ var WorkbookService = class WorkbookService extends SubmissionService {
27341
27527
  learnerId: request.learnerId,
27342
27528
  activityId: request.activityId
27343
27529
  } });
27344
- const summaries = /* @__PURE__ */ new Map();
27345
- const independentSeen = /* @__PURE__ */ new Set();
27530
+ const evidence = [];
27346
27531
  const ordered = [...attempts].sort((a, b) => Number(Boolean(a.previousAttemptId)) - Number(Boolean(b.previousAttemptId)) || String(a.id).localeCompare(String(b.id)));
27347
27532
  for (const attempt of ordered) {
27348
27533
  const revisions = await this.revisions(request, String(attempt.id));
@@ -27350,70 +27535,16 @@ var WorkbookService = class WorkbookService extends SubmissionService {
27350
27535
  const effective = confirmed ?? revisions.filter((r) => r.kind === "ai").at(-1);
27351
27536
  if (!effective) continue;
27352
27537
  const assessment = revisionView(effective).assessment;
27353
- for (const q of assessment.questions) for (const conceptId of q.conceptIds) {
27354
- const key = `${assessment.vocabulary.namespace}:${assessment.vocabulary.version}:${conceptId}`;
27355
- let summary = summaries.get(key);
27356
- if (!summary) {
27357
- summary = {
27358
- namespace: assessment.vocabulary.namespace,
27359
- version: assessment.vocabulary.version,
27360
- conceptId,
27361
- label: assessment.vocabulary.concepts.find((c) => c.id === conceptId).label,
27362
- confirmed: {
27363
- independent: 0,
27364
- assisted: 0,
27365
- unknown: 0,
27366
- correct: 0,
27367
- needsCorrection: 0,
27368
- unassessed: 0
27369
- },
27370
- provisional: 0,
27371
- episodes: [],
27372
- revisionIds: [],
27373
- cues: [],
27374
- observations: []
27375
- };
27376
- summaries.set(key, summary);
27377
- }
27378
- if (!summary.episodes.includes(attempt.episodeId)) summary.episodes.push(attempt.episodeId);
27379
- if (!summary.revisionIds.includes(String(effective.id))) summary.revisionIds.push(String(effective.id));
27380
- const observationAssistance = attempt.previousAttemptId && attempt.assistance === "independent" ? "unknown" : attempt.assistance;
27381
- summary.observations.push({
27382
- submissionId: attempt.submissionId,
27383
- revisionId: String(effective.id),
27384
- questionId: q.id,
27385
- episodeId: attempt.episodeId,
27386
- result: q.result,
27387
- assistance: observationAssistance,
27388
- isCorrection: Boolean(attempt.previousAttemptId),
27389
- capturedAt: attempt.capturedAt,
27390
- confirmed: Boolean(confirmed)
27391
- });
27392
- if (!confirmed) {
27393
- summary.provisional++;
27394
- continue;
27395
- }
27396
- if (q.result === "unassessed") {
27397
- summary.confirmed.unassessed++;
27398
- continue;
27399
- }
27400
- const episodeQuestion = `${key}:${attempt.episodeId}:${q.id}`;
27401
- const assistance = attempt.previousAttemptId && attempt.assistance === "independent" ? "unknown" : attempt.assistance;
27402
- if (assistance !== "independent" || !independentSeen.has(episodeQuestion)) summary.confirmed[assistance]++;
27403
- if (assistance === "independent") independentSeen.add(episodeQuestion);
27404
- if (q.result === "correct") summary.confirmed.correct++;
27405
- else summary.confirmed.needsCorrection++;
27406
- if (attempt.previousAttemptId && q.result === "correct" && !summary.cues.includes("improving on this item")) summary.cues.push("improving on this item");
27407
- }
27408
- }
27409
- for (const summary of summaries.values()) {
27410
- if (!summary.confirmed.independent) summary.cues.push("needs a fresh independent check");
27411
- if (summary.confirmed.needsCorrection) summary.cues.push("needs practice");
27412
- if (!summary.confirmed.correct && !summary.confirmed.needsCorrection) summary.cues.push("not enough evidence");
27538
+ evidence.push({
27539
+ attempt,
27540
+ revisionId: String(effective.id),
27541
+ assessment,
27542
+ confirmed: Boolean(confirmed)
27543
+ });
27413
27544
  }
27414
27545
  return {
27415
27546
  algorithmVersion: "workbook-descriptive-v1",
27416
- concepts: [...summaries.values()]
27547
+ concepts: summarizeWorkbookEvidence(evidence)
27417
27548
  };
27418
27549
  }
27419
27550
  };
@@ -27910,6 +28041,6 @@ var WorkbookPhotoService = class WorkbookPhotoService extends WorkbookService {
27910
28041
  }
27911
28042
  };
27912
28043
  //#endregion
27913
- export { CourseCollection as $, WorkbookFeedbackDelivery as A, AuthoredCourse as At, validateWorkbookAssessment as B, WorkbookActivity as C, SubmissionAsset as Ct, WorkbookAttempt as D, Enrollment as Dt, WorkbookAssessmentRevisionCollection as E, Curriculum as Et, WorkbookWorkFilingCollection as F, Lesson as Ft, FamilyIntakeCollection as G, FamilyIntake as H, WORKBOOK_MATH_VOCABULARY as I, Reading as It, familyIntakeScopeId as J, FamilyIntakeDecision as K, WorkbookAssessmentValidationError as L, Video as Lt, WorkbookFeedbackRelease as M, CourseSection as Mt, WorkbookFeedbackReleaseCollection as N, Exercise as Nt, WorkbookAttemptCollection as O, Learner as Ot, WorkbookWorkFiling as P, ExternalCourse as Pt, AuthoredCourseCollection as Q, WorkbookCompletionConflictError as R, Worksheet as Rt, Workbook as S, Submission as St, WorkbookAssessmentRevision as T, LessonAsset as Tt, FamilyIntakeAsset as U, workbookAggregateFeedback as V, FamilyIntakeAssetCollection as W, CatalogService as X, CatalogAuthorizationError as Y, AttestationCollection as Z, WorkbookWorkflowRequiredError as _, WorksheetCollection as _t, WorkbookContextRevisionCollection as a, ExternalCourseCollection as at, WorkbookCompletionDecision as b, Grade as bt, WorkbookPhotoDecision as c, LessonAssetCollection as ct, WorkbookPhotoIntakeCollection as d, ReadingCollection as dt, CourseSectionCollection as et, validateWorkbookPhotoIdentification as f, ScannedSubmissionCollection as ft, SubmissionStorageCleanupError as g, VideoCollection as gt, SubmissionService as h, SubmissionIntakeBindingCollection as ht, WorkbookContextRevision as i, ExternalCompletionCollection as it, WorkbookFeedbackDeliveryCollection as j, Course as jt, WorkbookCollection as k, LessonProgress as kt, WorkbookPhotoDecisionCollection as l, LessonCollection as lt, normalizeWorkbookAnswerRegions as m, SubmissionCollection as mt, preservePhotoProposal as n, EnrollmentCollection as nt, WorkbookPhotoAsset as o, GradeCollection as ot, normalizeWorkbookObservedLayout as p, SubmissionAssetCollection as pt, FamilyIntakeDecisionCollection as q, WorkbookService as r, ExerciseCollection as rt, WorkbookPhotoAssetCollection as s, LearnerCollection as st, WorkbookPhotoService as t, CurriculumCollection as tt, WorkbookPhotoIntake as u, LessonProgressCollection as ut, LearnerAuthorizationError as v, Attestation as vt, WorkbookActivityCollection as w, SubmissionIntakeBinding as wt, WorkbookCompletionDecisionCollection as x, ScannedSubmission as xt, LearnerService as y, ExternalCompletion as yt, aggregateWorkbookQuestions as z };
28044
+ export { FamilyIntakeDecision as $, WorkbookFeedbackDelivery as A, SubmissionAsset as At, validateWorkbookAssessment as B, Exercise as Bt, WorkbookActivity as C, VideoCollection as Ct, WorkbookAttempt as D, Grade as Dt, WorkbookAssessmentRevisionCollection as E, ExternalCompletion as Et, WorkbookWorkFilingCollection as F, Learner as Ft, GroupSharingConflictError as G, Worksheet as Gt, evaluateGroupSharingPolicy as H, Lesson as Ht, WORKBOOK_MATH_VOCABULARY as I, LessonProgress as It, validateGroupSharingPolicy as J, GroupSharingPolicyValidationError as K, WorkbookAssessmentValidationError as L, AuthoredCourse as Lt, WorkbookFeedbackRelease as M, LessonAsset as Mt, WorkbookFeedbackReleaseCollection as N, Curriculum as Nt, WorkbookAttemptCollection as O, ScannedSubmission as Ot, WorkbookWorkFiling as P, Enrollment as Pt, FamilyIntakeCollection as Q, WorkbookCompletionConflictError as R, Course as Rt, Workbook as S, SubmissionIntakeBindingCollection as St, WorkbookAssessmentRevision as T, Attestation as Tt, summarizeWorkbookEvidence as U, Reading as Ut, workbookAggregateFeedback as V, ExternalCourse as Vt, GroupSharingAuthorizationError as W, Video as Wt, FamilyIntakeAsset as X, FamilyIntake as Y, FamilyIntakeAssetCollection as Z, WorkbookWorkflowRequiredError as _, LessonProgressCollection as _t, WorkbookContextRevisionCollection as a, AuthoredCourseCollection as at, WorkbookCompletionDecision as b, SubmissionAssetCollection as bt, WorkbookPhotoDecision as c, CurriculumCollection as ct, WorkbookPhotoIntakeCollection as d, ExternalCompletionCollection as dt, FamilyIntakeDecisionCollection as et, validateWorkbookPhotoIdentification as f, ExternalCourseCollection as ft, SubmissionStorageCleanupError as g, LessonCollection as gt, SubmissionService as h, LessonAssetCollection as ht, WorkbookContextRevision as i, AttestationCollection as it, WorkbookFeedbackDeliveryCollection as j, SubmissionIntakeBinding as jt, WorkbookCollection as k, Submission as kt, WorkbookPhotoDecisionCollection as l, EnrollmentCollection as lt, normalizeWorkbookAnswerRegions as m, LearnerCollection as mt, preservePhotoProposal as n, CatalogAuthorizationError as nt, WorkbookPhotoAsset as o, CourseCollection as ot, normalizeWorkbookObservedLayout as p, GradeCollection as pt, canonicalGroupSharingPolicy as q, WorkbookService as r, CatalogService as rt, WorkbookPhotoAssetCollection as s, CourseSectionCollection as st, WorkbookPhotoService as t, familyIntakeScopeId as tt, WorkbookPhotoIntake as u, ExerciseCollection as ut, LearnerAuthorizationError as v, ReadingCollection as vt, WorkbookActivityCollection as w, WorksheetCollection as wt, WorkbookCompletionDecisionCollection as x, SubmissionCollection as xt, LearnerService as y, ScannedSubmissionCollection as yt, aggregateWorkbookQuestions as z, CourseSection as zt };
27914
28045
 
27915
- //# sourceMappingURL=workbook-photo-service-D5DUgh-C.js.map
28046
+ //# sourceMappingURL=workbook-photo-service-C6mbUUp6.js.map