@happyvertical/smrt-education 0.57.52 → 0.58.0
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/AGENTS.md +12 -7
- package/README.md +36 -2
- package/agents/course-work-design.md +144 -0
- package/agents/group-sharing.md +5 -1
- package/agents/learner-plan.md +3 -3
- package/agents/learner-service.md +1 -1
- package/agents/lesson-materials.md +52 -0
- package/agents/{workbook-service.md → lesson-work-service.md} +164 -95
- package/agents/{workbook-test-matrix.md → lesson-work-test-matrix.md} +3 -3
- package/agents/photo-intake.md +37 -50
- package/agents/question-records.md +1 -1
- package/agents/submission-service.md +22 -6
- package/dist/catalog-service.d.ts +1 -1
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog.d.ts +11 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/chunks/{workbook-photo-service-BtMQsbhq.js → workbook-photo-service-COtyQL1y.js} +5927 -4963
- package/dist/chunks/workbook-photo-service-COtyQL1y.js.map +1 -0
- package/dist/course-authoring-contracts.d.ts +5 -1
- package/dist/course-authoring-contracts.d.ts.map +1 -1
- package/dist/course-authoring-service.d.ts.map +1 -1
- package/dist/course-work-migration.d.ts +27 -0
- package/dist/course-work-migration.d.ts.map +1 -0
- package/dist/group-sharing-contracts.d.ts +3 -3
- package/dist/group-sharing-contracts.d.ts.map +1 -1
- package/dist/group-sharing-policy.d.ts +4 -4
- package/dist/group-sharing-policy.d.ts.map +1 -1
- package/dist/group-sharing-reader.d.ts.map +1 -1
- package/dist/index.d.ts +13 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +351 -36
- package/dist/index.js.map +1 -1
- package/dist/ingestion.d.ts +12 -11
- package/dist/ingestion.d.ts.map +1 -1
- package/dist/ingestion.js +63 -56
- package/dist/ingestion.js.map +1 -1
- package/dist/learner-plan-service.d.ts +1 -1
- package/dist/learner-plan-service.d.ts.map +1 -1
- package/dist/learner-service.d.ts +19 -5
- package/dist/learner-service.d.ts.map +1 -1
- package/dist/lesson-assets.d.ts +66 -2
- package/dist/lesson-assets.d.ts.map +1 -1
- package/dist/lesson-pages.d.ts +36 -0
- package/dist/lesson-pages.d.ts.map +1 -0
- package/dist/{workbook-completion.d.ts → lesson-work-completion.d.ts} +10 -10
- package/dist/lesson-work-completion.d.ts.map +1 -0
- package/dist/{workbook-contracts.d.ts → lesson-work-contracts.d.ts} +100 -52
- package/dist/lesson-work-contracts.d.ts.map +1 -0
- package/dist/lesson-work-models.d.ts +118 -0
- package/dist/lesson-work-models.d.ts.map +1 -0
- package/dist/lesson-work-service.d.ts +239 -0
- package/dist/lesson-work-service.d.ts.map +1 -0
- package/dist/manifest.json +6504 -5419
- package/dist/smrt-knowledge.json +2147 -1744
- package/dist/submission-service.d.ts +135 -5
- package/dist/submission-service.d.ts.map +1 -1
- package/dist/submissions.d.ts +2 -0
- package/dist/submissions.d.ts.map +1 -1
- package/dist/work-answer-regions.d.ts +11 -0
- package/dist/work-answer-regions.d.ts.map +1 -0
- package/dist/work-layout.d.ts +13 -0
- package/dist/work-layout.d.ts.map +1 -0
- package/dist/workbook-photo-contracts.d.ts +9 -7
- package/dist/workbook-photo-contracts.d.ts.map +1 -1
- package/dist/workbook-photo-ingestion.d.ts +1 -1
- package/dist/workbook-photo-ingestion.d.ts.map +1 -1
- package/dist/workbook-photo-models.d.ts +5 -5
- package/dist/workbook-photo-models.d.ts.map +1 -1
- package/dist/workbook-photo-service.d.ts +13 -10
- package/dist/workbook-photo-service.d.ts.map +1 -1
- package/package.json +8 -8
- package/dist/chunks/workbook-photo-service-BtMQsbhq.js.map +0 -1
- package/dist/workbook-answer-regions.d.ts +0 -11
- package/dist/workbook-answer-regions.d.ts.map +0 -1
- package/dist/workbook-completion.d.ts.map +0 -1
- package/dist/workbook-contracts.d.ts.map +0 -1
- package/dist/workbook-layout.d.ts +0 -13
- package/dist/workbook-layout.d.ts.map +0 -1
- package/dist/workbook-models.d.ts +0 -129
- package/dist/workbook-models.d.ts.map +0 -1
- package/dist/workbook-service.d.ts +0 -163
- package/dist/workbook-service.d.ts.map +0 -1
package/dist/ingestion.d.ts
CHANGED
|
@@ -33,12 +33,12 @@ export declare function processEducationAnalysis(input: {
|
|
|
33
33
|
}>;
|
|
34
34
|
import type { IngestionScope } from '@happyvertical/smrt-ingestion/server';
|
|
35
35
|
import type { LearnerRequest } from './learner-service.js';
|
|
36
|
+
import { type WorkAssessment } from './lesson-work-contracts.js';
|
|
37
|
+
import { LessonWorkService } from './lesson-work-service.js';
|
|
36
38
|
import type { SubmissionService } from './submission-service.js';
|
|
37
|
-
import { type WorkbookAssessment } from './workbook-contracts.js';
|
|
38
|
-
import { WorkbookService } from './workbook-service.js';
|
|
39
39
|
export type EducationIntakeCaptureRequest = LearnerRequest & {
|
|
40
|
-
|
|
41
|
-
assistance?: import('./
|
|
40
|
+
lessonActivityId?: string;
|
|
41
|
+
assistance?: import('./lesson-work-contracts.js').WorkAssistance;
|
|
42
42
|
enrollmentId: string;
|
|
43
43
|
lessonId: string;
|
|
44
44
|
itemId: string;
|
|
@@ -70,6 +70,7 @@ export declare function captureEducationIntake(input: {
|
|
|
70
70
|
pages: ReadonlyArray<{
|
|
71
71
|
assetId: string;
|
|
72
72
|
sortOrder: number;
|
|
73
|
+
pageKey: string;
|
|
73
74
|
}>;
|
|
74
75
|
awaitingParentRelease?: boolean;
|
|
75
76
|
sourceRequiresParentRelease?: boolean;
|
|
@@ -94,8 +95,8 @@ export declare function educationIntakeHandlers(input: {
|
|
|
94
95
|
};
|
|
95
96
|
/** After capture, bind a new grading stage to this exact immutable attempt. */
|
|
96
97
|
submissionId?: string;
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
lessonActivityId?: string;
|
|
99
|
+
workVocabulary?: WorkAssessment['vocabulary'];
|
|
99
100
|
}): OperationHandler[];
|
|
100
101
|
/** Owning education target resolver for ingestion's transactional host hook.
|
|
101
102
|
* Serializes with education enrollment mutations, then performs public service
|
|
@@ -109,7 +110,7 @@ export declare function educationIntakeTargetResolver(input: {
|
|
|
109
110
|
};
|
|
110
111
|
submissionId?: string;
|
|
111
112
|
}): import('@happyvertical/smrt-ingestion/execution').IntakeExecutionOptions['assertTarget'];
|
|
112
|
-
export type
|
|
113
|
+
export type WorkPreservationResult = {
|
|
113
114
|
kind: 'pending';
|
|
114
115
|
} | {
|
|
115
116
|
kind: 'unavailable';
|
|
@@ -127,16 +128,16 @@ export type WorkbookPreservationResult = {
|
|
|
127
128
|
* never an educational mark or a generic server error. Education authorization,
|
|
128
129
|
* integrity and DB errors throw; unavailable scratch never grants raw access.
|
|
129
130
|
*/
|
|
130
|
-
export declare function
|
|
131
|
+
export declare function preserveWorkIntakeAnalysis(input: {
|
|
131
132
|
ingestion: IngestionService;
|
|
132
|
-
workbooks:
|
|
133
|
+
workbooks: LessonWorkService;
|
|
133
134
|
request: LearnerRequest & {
|
|
134
135
|
enrollmentId: string;
|
|
135
136
|
};
|
|
136
137
|
activityId: string;
|
|
137
138
|
submissionId: string;
|
|
138
|
-
|
|
139
|
-
}): Promise<
|
|
139
|
+
workVocabulary?: WorkAssessment['vocabulary'];
|
|
140
|
+
}): Promise<WorkPreservationResult>;
|
|
140
141
|
export { receiveFamilyIntake, retainFamilyIntakeReceipt, } from './family-intake-ingestion.js';
|
|
141
142
|
export { FamilyIntakeService } from './family-intake-service.js';
|
|
142
143
|
export { educationQuestionExtractionHandler, preserveQuestionExtractionAnalysis, QUESTION_EXTRACTION_HANDLER_ID, questionExtractionGrounding, } from './question-extraction-ingestion.js';
|
package/dist/ingestion.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingestion.d.ts","sourceRoot":"","sources":["../src/ingestion.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EAGrB,KAAK,gBAAgB,EAGtB,MAAM,sCAAsC,CAAC;AAM9C,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAEvE,oFAAoF;AACpF,MAAM,WAAW,gCAAgC;IAC/C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,gBAAgB,EAC3B,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,gCAAgC,EAC/C,YAAY,CAAC,EAAE,MAAM;;;GAOtB;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,KAAK,EAAE;IACpD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,gCAAgC,CAAC;CAC9C,GAAG,OAAO,CACP;IAAE,IAAI,EAAE,WAAW,GAAG,uBAAuB,GAAG,aAAa,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAClD,CAsCA;AAGD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAG3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ingestion.d.ts","sourceRoot":"","sources":["../src/ingestion.ts"],"names":[],"mappings":"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"}
|
package/dist/ingestion.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { F as normalizeWorkObservedLayout, I as normalizeWorkAnswerRegions, It as FamilyIntakeAssetCollection, Lt as FamilyIntakeCollection, M as QuestionExtractionPendingError, N as QuestionExtractionValidationError, Nt as WorkbookPhotoIntakeCollection, P as LessonWorkService, Tt as familyIntakeScopeId, Y as LearnerAuthorizationError, a as questionExtractionProposalDigest, an as aggregateWorkQuestions, kt as WorkbookPhotoAssetCollection, n as preservePhotoProposal, nn as WORK_MATH_VOCABULARY, on as validateWorkAssessment, r as validateWorkbookPhotoIdentification, rn as WorkAssessmentValidationError, sn as workAggregateFeedback, t as WorkbookPhotoService, u as QUESTION_EXTRACTION_ARGS_SCHEMA, zt as FamilyIntakeDecisionCollection } from "./chunks/workbook-photo-service-COtyQL1y.js";
|
|
2
2
|
import { getCurrentTenant, withTenant } from "@happyvertical/smrt-tenancy";
|
|
3
3
|
import { withPrivateAssetAccess } from "@happyvertical/smrt-assets";
|
|
4
4
|
import { registerPermissionDefinitions } from "@happyvertical/smrt-users";
|
|
@@ -778,19 +778,19 @@ function workbookPhotoIntakeHandlers(input) {
|
|
|
778
778
|
],
|
|
779
779
|
references: { workbookId: {
|
|
780
780
|
kind: "candidate",
|
|
781
|
-
model: "@happyvertical/smrt-education:
|
|
781
|
+
model: "@happyvertical/smrt-education:Course"
|
|
782
782
|
} },
|
|
783
783
|
candidates: async (query, context, page) => {
|
|
784
784
|
await authorize(context);
|
|
785
|
-
const books = await bound(context, (service) => service.
|
|
785
|
+
const books = await bound(context, (service) => service.listPhotoBookCourses(input.request));
|
|
786
786
|
const needle = query.toLowerCase();
|
|
787
787
|
const ordered = [...books].sort((a, b) => Number(b.title.toLowerCase().includes(needle)) - Number(a.title.toLowerCase().includes(needle)) || a.id.localeCompare(b.id));
|
|
788
788
|
return {
|
|
789
789
|
items: ordered.slice(0, page.limit).map((book) => ({
|
|
790
|
-
model: "@happyvertical/smrt-education:
|
|
790
|
+
model: "@happyvertical/smrt-education:Course",
|
|
791
791
|
id: book.id,
|
|
792
792
|
revision: workbookRevision(book),
|
|
793
|
-
label: JSON.stringify(book)
|
|
793
|
+
label: JSON.stringify(wireCandidate(book))
|
|
794
794
|
})),
|
|
795
795
|
hasMore: ordered.length > page.limit
|
|
796
796
|
};
|
|
@@ -821,12 +821,19 @@ function workbookPhotoIntakeHandlers(input) {
|
|
|
821
821
|
}
|
|
822
822
|
}];
|
|
823
823
|
}
|
|
824
|
+
function wireCandidate(book) {
|
|
825
|
+
const { bookKey, ...rest } = book;
|
|
826
|
+
return {
|
|
827
|
+
...rest,
|
|
828
|
+
workbookKey: bookKey
|
|
829
|
+
};
|
|
830
|
+
}
|
|
824
831
|
function workbookRevision(book) {
|
|
825
832
|
return `workbook:${createHash("sha256").update(canonical(book)).digest("hex")}`;
|
|
826
833
|
}
|
|
827
834
|
function workbookPhotoIntakeTargetResolver(input) {
|
|
828
835
|
return async ({ db, scope, itemId, model, id, revision }) => {
|
|
829
|
-
if (model !== "@happyvertical/smrt-education:
|
|
836
|
+
if (model !== "@happyvertical/smrt-education:Course") throw new Error("Photo target unavailable");
|
|
830
837
|
const book = (await withTenant({
|
|
831
838
|
...getCurrentTenant(),
|
|
832
839
|
tenantId: input.request.tenantId,
|
|
@@ -834,7 +841,7 @@ function workbookPhotoIntakeTargetResolver(input) {
|
|
|
834
841
|
superAdminBypass: false
|
|
835
842
|
}, () => input.workbooks.withExecutor(db, async (service) => {
|
|
836
843
|
await service.assertWorkbookPhotoScope(input.request, scope, itemId);
|
|
837
|
-
return service.
|
|
844
|
+
return service.listPhotoBookCourses(input.request);
|
|
838
845
|
}))).find((book2) => book2.id === id);
|
|
839
846
|
if (!book) throw new Error("Photo target unavailable");
|
|
840
847
|
const current = workbookRevision(book);
|
|
@@ -1004,11 +1011,11 @@ async function captureEducationIntake(input) {
|
|
|
1004
1011
|
previousSubmissionId: request.previousSubmissionId
|
|
1005
1012
|
}
|
|
1006
1013
|
};
|
|
1007
|
-
if (request.
|
|
1008
|
-
if (!(submissions instanceof
|
|
1009
|
-
return (await submissions.
|
|
1014
|
+
if (request.lessonActivityId) {
|
|
1015
|
+
if (!(submissions instanceof LessonWorkService) || request.kind !== "completed_work") throw new Error("Lesson work intake requires LessonWorkService completed work");
|
|
1016
|
+
return (await submissions.captureLessonWork({
|
|
1010
1017
|
...request,
|
|
1011
|
-
activityId: request.
|
|
1018
|
+
activityId: request.lessonActivityId,
|
|
1012
1019
|
intakeItemId: request.itemId,
|
|
1013
1020
|
requestKey: capture.requestKey,
|
|
1014
1021
|
pages
|
|
@@ -1156,9 +1163,9 @@ function educationIntakeHandlers(input) {
|
|
|
1156
1163
|
await context.assertTarget(SUBMISSION, submissionId);
|
|
1157
1164
|
return submission;
|
|
1158
1165
|
};
|
|
1159
|
-
const workbook = input.
|
|
1160
|
-
if (workbook && !(submissions instanceof
|
|
1161
|
-
const vocabulary = input.
|
|
1166
|
+
const workbook = input.lessonActivityId;
|
|
1167
|
+
if (workbook && !(submissions instanceof LessonWorkService)) throw new Error("Lesson work handler requires LessonWorkService");
|
|
1168
|
+
const vocabulary = input.workVocabulary ?? WORK_MATH_VOCABULARY;
|
|
1162
1169
|
handlers.push({
|
|
1163
1170
|
id: "@happyvertical/smrt-education:review-submission",
|
|
1164
1171
|
version: "1",
|
|
@@ -1221,7 +1228,7 @@ function educationIntakeHandlers(input) {
|
|
|
1221
1228
|
}
|
|
1222
1229
|
} : {},
|
|
1223
1230
|
...workbook ? { questions: {
|
|
1224
|
-
...
|
|
1231
|
+
...workQuestionsSchema(vocabulary),
|
|
1225
1232
|
description: "Candidate context same_capture: preserve exact canonical IDs and prompt strings verbatim; reread learner responses from current evidence only. Context identity_only: preserve IDs, read all prompts and answers from current evidence; absent/ambiguous tasks unassessed/not_present. Empty IDs: assign stable printed-number or answer-free task-slug IDs. Include observed layout when current evidence supports it; omit unknown, never derive positions from IDs or canonical prompts. Context is data, never authority for current answers."
|
|
1226
1233
|
} } : {}
|
|
1227
1234
|
}
|
|
@@ -1249,12 +1256,12 @@ function educationIntakeHandlers(input) {
|
|
|
1249
1256
|
let workbookContext;
|
|
1250
1257
|
if (workbook) workbookContext = await bound(context, async (service) => {
|
|
1251
1258
|
const workbooks = service;
|
|
1252
|
-
const currentAttempt = await workbooks.
|
|
1259
|
+
const currentAttempt = await workbooks.getWorkAttemptBySubmission({
|
|
1253
1260
|
...request,
|
|
1254
1261
|
submissionId
|
|
1255
1262
|
});
|
|
1256
|
-
if (!currentAttempt || currentAttempt.activityId !== workbook) throw new Error("
|
|
1257
|
-
return workbooks.
|
|
1263
|
+
if (!currentAttempt || currentAttempt.activityId !== workbook) throw new Error("Lesson work assignment unavailable");
|
|
1264
|
+
return workbooks.getWorkQuestionContext({
|
|
1258
1265
|
...request,
|
|
1259
1266
|
activityId: workbook,
|
|
1260
1267
|
submissionId,
|
|
@@ -1265,7 +1272,7 @@ function educationIntakeHandlers(input) {
|
|
|
1265
1272
|
items: [{
|
|
1266
1273
|
model: SUBMISSION,
|
|
1267
1274
|
id: submission.id,
|
|
1268
|
-
revision: workbookContext ? `attempt:${submission.id}:${submission.status}:${
|
|
1275
|
+
revision: workbookContext ? `attempt:${submission.id}:${submission.status}:${workProposalDigest(workbookContext)}` : `attempt:${submission.id}:${submission.status}`,
|
|
1269
1276
|
label: "Current captured attempt (guardian review required).",
|
|
1270
1277
|
...workbookContext ? { context: workbookContext } : {}
|
|
1271
1278
|
}],
|
|
@@ -1280,13 +1287,13 @@ function educationIntakeHandlers(input) {
|
|
|
1280
1287
|
preview: async (args, context) => {
|
|
1281
1288
|
const submission = await requireSubmission(args, context);
|
|
1282
1289
|
const revision = workbook ? await bound(context, async (service) => {
|
|
1283
|
-
const current2 = await service.
|
|
1290
|
+
const current2 = await service.getWorkQuestionContext({
|
|
1284
1291
|
...request,
|
|
1285
1292
|
activityId: workbook,
|
|
1286
1293
|
submissionId,
|
|
1287
1294
|
analysisRevision: context.analysisRevision
|
|
1288
1295
|
});
|
|
1289
|
-
return `attempt:${submission.id}:${submission.status}:${
|
|
1296
|
+
return `attempt:${submission.id}:${submission.status}:${workProposalDigest(current2)}`;
|
|
1290
1297
|
}) : `attempt:${submission.id}:${submission.status}`;
|
|
1291
1298
|
return {
|
|
1292
1299
|
display: {
|
|
@@ -1315,7 +1322,7 @@ function educationIntakeHandlers(input) {
|
|
|
1315
1322
|
let cursor;
|
|
1316
1323
|
let attempt;
|
|
1317
1324
|
do {
|
|
1318
|
-
const timeline = await workbooks.
|
|
1325
|
+
const timeline = await workbooks.getWorkTimeline({
|
|
1319
1326
|
...request,
|
|
1320
1327
|
activityId: workbook,
|
|
1321
1328
|
...cursor ? { cursor } : {}
|
|
@@ -1323,9 +1330,9 @@ function educationIntakeHandlers(input) {
|
|
|
1323
1330
|
attempt = timeline.attempts.find((entry) => entry.submissionId === submissionId);
|
|
1324
1331
|
cursor = timeline.nextCursor ?? void 0;
|
|
1325
1332
|
} while (!attempt && cursor);
|
|
1326
|
-
const proposal = attempt?.revisions.find((revision) => revision.kind === "ai" && revision.assessment.provenance.proposalDigest ===
|
|
1327
|
-
if (!proposal) throw new Error("
|
|
1328
|
-
await workbooks.
|
|
1333
|
+
const proposal = attempt?.revisions.find((revision) => revision.kind === "ai" && revision.assessment.provenance.proposalDigest === workProposalDigest(args));
|
|
1334
|
+
if (!proposal) throw new Error("Lesson work analysis must be preserved before guardian apply");
|
|
1335
|
+
await workbooks.reviewWorkAssessment({
|
|
1329
1336
|
...request,
|
|
1330
1337
|
activityId: workbook,
|
|
1331
1338
|
submissionId,
|
|
@@ -1370,19 +1377,19 @@ function educationIntakeTargetResolver(input) {
|
|
|
1370
1377
|
submissionId: id
|
|
1371
1378
|
});
|
|
1372
1379
|
if (submission.intakeItemId !== itemId || submission.kind !== "completed_work") throw new Error("Education intake/attempt binding mismatch");
|
|
1373
|
-
if (service instanceof
|
|
1374
|
-
const attempt = await service.
|
|
1380
|
+
if (service instanceof LessonWorkService) {
|
|
1381
|
+
const attempt = await service.getWorkAttemptBySubmission({
|
|
1375
1382
|
...request,
|
|
1376
1383
|
submissionId: id
|
|
1377
1384
|
});
|
|
1378
1385
|
if (attempt) {
|
|
1379
|
-
const context = await service.
|
|
1386
|
+
const context = await service.getWorkQuestionContext({
|
|
1380
1387
|
...request,
|
|
1381
1388
|
activityId: attempt.activityId,
|
|
1382
1389
|
submissionId: id,
|
|
1383
1390
|
analysisRevision
|
|
1384
1391
|
});
|
|
1385
|
-
return `attempt:${submission.id}:${submission.status}:${
|
|
1392
|
+
return `attempt:${submission.id}:${submission.status}:${workProposalDigest(context)}`;
|
|
1386
1393
|
}
|
|
1387
1394
|
}
|
|
1388
1395
|
return `attempt:${submission.id}:${submission.status}`;
|
|
@@ -1391,11 +1398,11 @@ function educationIntakeTargetResolver(input) {
|
|
|
1391
1398
|
return { revision: current };
|
|
1392
1399
|
};
|
|
1393
1400
|
}
|
|
1394
|
-
function
|
|
1401
|
+
function workProposalDigest(value) {
|
|
1395
1402
|
const canonical = (v) => Array.isArray(v) ? v.map(canonical) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b)).map(([k, x]) => [k, canonical(x)])) : v;
|
|
1396
1403
|
return createHash("sha256").update(JSON.stringify(canonical(value))).digest("hex");
|
|
1397
1404
|
}
|
|
1398
|
-
function
|
|
1405
|
+
function workQuestionsSchema(vocabulary) {
|
|
1399
1406
|
return {
|
|
1400
1407
|
type: "array",
|
|
1401
1408
|
minItems: 1,
|
|
@@ -1552,15 +1559,15 @@ function educationEvidenceGrounding(snapshot) {
|
|
|
1552
1559
|
sources
|
|
1553
1560
|
};
|
|
1554
1561
|
}
|
|
1555
|
-
async function
|
|
1562
|
+
async function preserveWorkIntakeAnalysis(input) {
|
|
1556
1563
|
const { ingestion, workbooks, request, activityId, submissionId } = input;
|
|
1557
|
-
const attempt = await workbooks.
|
|
1564
|
+
const attempt = await workbooks.getWorkAttemptBySubmission({
|
|
1558
1565
|
...request,
|
|
1559
1566
|
submissionId
|
|
1560
1567
|
});
|
|
1561
|
-
if (!attempt || attempt.activityId !== activityId || !attempt.submission.intakeItemId) throw new Error("
|
|
1568
|
+
if (!attempt || attempt.activityId !== activityId || !attempt.submission.intakeItemId) throw new Error("Lesson work intake binding unavailable");
|
|
1562
1569
|
const itemId = attempt.submission.intakeItemId;
|
|
1563
|
-
const state = await workbooks.
|
|
1570
|
+
const state = await workbooks.getWorkPreservationState({
|
|
1564
1571
|
...request,
|
|
1565
1572
|
activityId,
|
|
1566
1573
|
submissionId
|
|
@@ -1613,40 +1620,40 @@ async function preserveWorkbookIntakeAnalysis(input) {
|
|
|
1613
1620
|
kind: "retained",
|
|
1614
1621
|
revisionId: importedAttempt.assessmentRevisionId
|
|
1615
1622
|
};
|
|
1616
|
-
const sources = await workbooks.
|
|
1623
|
+
const sources = await workbooks.getWorkSourceAnchors({
|
|
1617
1624
|
...request,
|
|
1618
1625
|
activityId,
|
|
1619
1626
|
submissionId
|
|
1620
1627
|
});
|
|
1621
1628
|
const originals = intakeOriginalEvidence(snapshot.evidence).filter((e) => e.mediaType !== "application/json");
|
|
1622
|
-
if (originals.length !== sources.length || originals.some((e, i) => e.contentHash !== sources[i].sha256)) throw new Error("
|
|
1629
|
+
if (originals.length !== sources.length || originals.some((e, i) => e.contentHash !== sources[i].sha256)) throw new Error("Lesson work retained source mismatch");
|
|
1623
1630
|
let assessment;
|
|
1624
1631
|
try {
|
|
1625
1632
|
const generation = snapshot.result.output.proposals;
|
|
1626
|
-
if (!generation || generation.version !== 1 || !Array.isArray(generation.suggestions)) throw new
|
|
1633
|
+
if (!generation || generation.version !== 1 || !Array.isArray(generation.suggestions)) throw new WorkAssessmentValidationError("Malformed workbook generation");
|
|
1627
1634
|
if (!["proposals", "needs_review"].includes(generation.outcome)) return {
|
|
1628
1635
|
kind: "needs_review",
|
|
1629
1636
|
reason: generation.outcome === "ambiguous" ? "ambiguous_proposal" : "invalid_assessment"
|
|
1630
1637
|
};
|
|
1631
1638
|
const suggestions = generation.suggestions.filter((s) => s.handlerId === "@happyvertical/smrt-education:review-submission");
|
|
1632
|
-
if (suggestions.length !== 1) throw new
|
|
1639
|
+
if (suggestions.length !== 1) throw new WorkAssessmentValidationError("Expected one workbook proposal", "ambiguous_proposal");
|
|
1633
1640
|
const suggestion = suggestions[0];
|
|
1634
|
-
if (suggestion.args.submissionId !== submissionId || !Array.isArray(suggestion.args.questions) || suggestion.missingFields.length) throw new
|
|
1641
|
+
if (suggestion.args.submissionId !== submissionId || !Array.isArray(suggestion.args.questions) || suggestion.missingFields.length) throw new WorkAssessmentValidationError("Incomplete workbook question proposal");
|
|
1635
1642
|
const questionSchema = generation.offered.find((entry) => entry.handler.id === suggestion.handlerId)?.handler.argsSchema.properties?.questions;
|
|
1636
|
-
if (!questionSchema) throw new
|
|
1637
|
-
const curated = input.
|
|
1638
|
-
if (JSON.stringify(questionSchema.items.properties.conceptIds.items.enum) !== JSON.stringify(curated.concepts.map((c) => c.id))) throw new
|
|
1643
|
+
if (!questionSchema) throw new WorkAssessmentValidationError("Generation did not use workbook question contract");
|
|
1644
|
+
const curated = input.workVocabulary ?? WORK_MATH_VOCABULARY;
|
|
1645
|
+
if (JSON.stringify(questionSchema.items.properties.conceptIds.items.enum) !== JSON.stringify(curated.concepts.map((c) => c.id))) throw new WorkAssessmentValidationError("Unsupported workbook vocabulary");
|
|
1639
1646
|
const grounding = snapshot.configuration.educationEvidenceGrounding;
|
|
1640
1647
|
const trustedGrounding = grounding?.version === 1 && grounding.extractionAttemptId === generation.source?.attemptId ? grounding : void 0;
|
|
1641
1648
|
const questions = suggestion.args.questions.map((value) => {
|
|
1642
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new
|
|
1649
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new WorkAssessmentValidationError("Invalid workbook question");
|
|
1643
1650
|
const q = value;
|
|
1644
|
-
if (!Array.isArray(q.anchors)) throw new
|
|
1651
|
+
if (!Array.isArray(q.anchors)) throw new WorkAssessmentValidationError("Missing workbook source anchors", "ungrounded_source");
|
|
1645
1652
|
const limitations = new Set(Array.isArray(q.evidenceLimitations) ? q.evidenceLimitations : []);
|
|
1646
1653
|
const anchors = q.anchors.map((value2) => {
|
|
1647
1654
|
const anchor = value2;
|
|
1648
1655
|
const index = originals.findIndex((e) => e.id === anchor?.evidenceId);
|
|
1649
|
-
if (index < 0) throw new
|
|
1656
|
+
if (index < 0) throw new WorkAssessmentValidationError("Question must cite a captured original", "ungrounded_source");
|
|
1650
1657
|
const page = originals[index].mediaType?.startsWith("image/") ? 1 : null;
|
|
1651
1658
|
const level = "source";
|
|
1652
1659
|
limitations.add("Source-level evidence only; no verified question page or region was supplied by extraction.");
|
|
@@ -1724,8 +1731,8 @@ async function preserveWorkbookIntakeAnalysis(input) {
|
|
|
1724
1731
|
version: 1,
|
|
1725
1732
|
feedback: "",
|
|
1726
1733
|
questions,
|
|
1727
|
-
...
|
|
1728
|
-
...
|
|
1734
|
+
...normalizeWorkObservedLayout(observedLayout, questions, sources),
|
|
1735
|
+
...normalizeWorkAnswerRegions(mappedRegions, questions, sources),
|
|
1729
1736
|
vocabulary: structuredClone(curated),
|
|
1730
1737
|
provenance: {
|
|
1731
1738
|
provider: generation.provenance.generative.provider,
|
|
@@ -1735,18 +1742,18 @@ async function preserveWorkbookIntakeAnalysis(input) {
|
|
|
1735
1742
|
inputHash: snapshot.inputDigest,
|
|
1736
1743
|
analysisId: snapshot.attemptId,
|
|
1737
1744
|
analysisRevision: snapshot.revision,
|
|
1738
|
-
proposalDigest:
|
|
1745
|
+
proposalDigest: workProposalDigest(suggestion.args),
|
|
1739
1746
|
providerVersion: generation.provenance.generative.version,
|
|
1740
1747
|
sourceAnalysis: generation.source,
|
|
1741
1748
|
catalogDigest: generation.provenance.catalogDigest,
|
|
1742
1749
|
policyVersions: generation.provenance.policyVersions
|
|
1743
1750
|
}
|
|
1744
1751
|
};
|
|
1745
|
-
|
|
1746
|
-
assessment.aggregate =
|
|
1747
|
-
assessment.feedback =
|
|
1752
|
+
validateWorkAssessment(assessment);
|
|
1753
|
+
assessment.aggregate = aggregateWorkQuestions(assessment.questions);
|
|
1754
|
+
assessment.feedback = workAggregateFeedback(assessment.aggregate);
|
|
1748
1755
|
} catch (error) {
|
|
1749
|
-
if (error instanceof
|
|
1756
|
+
if (error instanceof WorkAssessmentValidationError) return {
|
|
1750
1757
|
kind: "needs_review",
|
|
1751
1758
|
reason: error.reason
|
|
1752
1759
|
};
|
|
@@ -1759,7 +1766,7 @@ async function preserveWorkbookIntakeAnalysis(input) {
|
|
|
1759
1766
|
try {
|
|
1760
1767
|
return {
|
|
1761
1768
|
kind: "preserved",
|
|
1762
|
-
revisionId: (await workbooks.
|
|
1769
|
+
revisionId: (await workbooks.recordWorkAnalysis({
|
|
1763
1770
|
...request,
|
|
1764
1771
|
activityId,
|
|
1765
1772
|
submissionId,
|
|
@@ -1768,7 +1775,7 @@ async function preserveWorkbookIntakeAnalysis(input) {
|
|
|
1768
1775
|
})).id
|
|
1769
1776
|
};
|
|
1770
1777
|
} catch (error) {
|
|
1771
|
-
if (error instanceof
|
|
1778
|
+
if (error instanceof WorkAssessmentValidationError) return {
|
|
1772
1779
|
kind: "needs_review",
|
|
1773
1780
|
reason: error.reason
|
|
1774
1781
|
};
|
|
@@ -1776,6 +1783,6 @@ async function preserveWorkbookIntakeAnalysis(input) {
|
|
|
1776
1783
|
}
|
|
1777
1784
|
}
|
|
1778
1785
|
//#endregion
|
|
1779
|
-
export { FamilyIntakeService, QUESTION_EXTRACTION_HANDLER_ID, WORKBOOK_PHOTO_HANDLER, captureEducationIntake, educationIntakeHandlers, educationIntakeTargetResolver, educationQuestionExtractionHandler, preserveQuestionExtractionAnalysis,
|
|
1786
|
+
export { FamilyIntakeService, QUESTION_EXTRACTION_HANDLER_ID, WORKBOOK_PHOTO_HANDLER, captureEducationIntake, educationIntakeHandlers, educationIntakeTargetResolver, educationQuestionExtractionHandler, preserveQuestionExtractionAnalysis, preserveWorkIntakeAnalysis, preserveWorkbookPhotoIdentification, processEducationAnalysis, processWorkbookPhotoAnalysis, questionExtractionGrounding, queueEducationExtraction, receiveFamilyIntake, receiveWorkbookPhotoIntake, requireIngestionMemoryLimit, retainFamilyIntakeReceipt, retainWorkbookPhotoReceipt, workbookPhotoIntakeHandlers, workbookPhotoIntakeTargetResolver };
|
|
1780
1787
|
|
|
1781
1788
|
//# sourceMappingURL=ingestion.js.map
|