@happyvertical/smrt-education 0.57.45 → 0.57.47
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 +5 -0
- package/README.md +7 -1
- package/agents/learner-service.md +31 -2
- package/agents/question-extraction.md +108 -0
- package/agents/submission-service.md +15 -0
- package/dist/chunks/{workbook-photo-service-DKgoUHeb.js → workbook-photo-service-B7Tujv__.js} +6494 -1153
- package/dist/chunks/workbook-photo-service-B7Tujv__.js.map +1 -0
- package/dist/collections.d.ts +4 -1
- package/dist/collections.d.ts.map +1 -1
- package/dist/course-authoring-contracts.d.ts +1 -1
- package/dist/course-authoring-contracts.d.ts.map +1 -1
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1049 -4628
- package/dist/index.js.map +1 -1
- package/dist/ingestion.d.ts +2 -0
- package/dist/ingestion.d.ts.map +1 -1
- package/dist/ingestion.js +197 -2
- package/dist/ingestion.js.map +1 -1
- package/dist/learner-history.d.ts +50 -0
- package/dist/learner-history.d.ts.map +1 -0
- package/dist/learner-service.d.ts +78 -2
- package/dist/learner-service.d.ts.map +1 -1
- package/dist/manifest.json +4255 -1444
- package/dist/question-extraction-contracts.d.ts +231 -0
- package/dist/question-extraction-contracts.d.ts.map +1 -0
- package/dist/question-extraction-ingestion.d.ts +44 -0
- package/dist/question-extraction-ingestion.d.ts.map +1 -0
- package/dist/question-extraction-models.d.ts +56 -0
- package/dist/question-extraction-models.d.ts.map +1 -0
- package/dist/question-extraction-service.d.ts +71 -0
- package/dist/question-extraction-service.d.ts.map +1 -0
- package/dist/question-extraction.d.ts +317 -0
- package/dist/question-extraction.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +961 -143
- package/dist/submission-service.d.ts +34 -1
- package/dist/submission-service.d.ts.map +1 -1
- package/dist/submissions.d.ts +25 -1
- package/dist/submissions.d.ts.map +1 -1
- package/package.json +10 -10
- package/dist/chunks/workbook-photo-service-DKgoUHeb.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -47,6 +47,11 @@ render ids and exact-revision references use [QuestionAuthoringService](agents/q
|
|
|
47
47
|
Keep keys out of learner bundles; a missing key stays null. `attachGeneratedPrintable`
|
|
48
48
|
stores the rendered PDFs; the guardian copy is always an `answer-key`, never `printable`.
|
|
49
49
|
|
|
50
|
+
Structured questions from scans and imported course files use
|
|
51
|
+
[QuestionExtractionService](agents/question-extraction.md) and the `extract-questions`
|
|
52
|
+
ingestion handler. Anchors come from provider region catalogs only; match by worksheet
|
|
53
|
+
id and layout, never text; ambiguous matches stay unattached; source-keyed replays.
|
|
54
|
+
|
|
50
55
|
Source import: [host boundary and transaction contract](agents/source-import.md).
|
|
51
56
|
`SourceImportService` requires host opt-in, matching active actor/tenant and
|
|
52
57
|
explicit licensing authorization; never add a system/global write bypass.
|
package/README.md
CHANGED
|
@@ -98,6 +98,10 @@ can `getLearner`, `startLesson`, and `listProgress` for their own work.
|
|
|
98
98
|
Guardian access requires a same-tenant directed `ProfileRelationship` plus the
|
|
99
99
|
host's permission decision. Sibling membership does not grant access.
|
|
100
100
|
|
|
101
|
+
`putBackLesson` returns started work to `not_started` and `setEnrollmentStatus`
|
|
102
|
+
starts, pauses, resumes or drops an enrollment along a fixed transition table;
|
|
103
|
+
both append immutable history (actor, and for status the host policy basis)
|
|
104
|
+
and never touch submissions, grades or other progress.
|
|
101
105
|
Repeated starts preserve the existing progress, including attempts and scores.
|
|
102
106
|
A new curriculum/year creates a distinct enrollment and fresh progress history.
|
|
103
107
|
An unscored lesson has `bestScore: null`, distinct from zero. The service does
|
|
@@ -114,7 +118,9 @@ integration on both SQLite and PostgreSQL.
|
|
|
114
118
|
|
|
115
119
|
`SubmissionService` captures tenant-owned uploads, reviews or attests work with
|
|
116
120
|
optional guardian scores, signs authorized asset claims, and splits private
|
|
117
|
-
PDF packets by page range.
|
|
121
|
+
PDF packets by page range. `attestOwnLesson` lets a learner mark their own
|
|
122
|
+
Reading or Video lesson done as a self-reported, ungraded attestation that a
|
|
123
|
+
guardian then confirms. It reuses learner ownership and transaction checks.
|
|
118
124
|
See the [submission integration contract](agents/submission-service.md) for
|
|
119
125
|
inputs, retry rules, signing requirements and external-storage compensation.
|
|
120
126
|
|
|
@@ -71,7 +71,36 @@ Same-tenant membership alone never grants sibling access.
|
|
|
71
71
|
- `startLesson` allows the learner or authorized guardian to create their own
|
|
72
72
|
enrollment's lesson progress in `in_progress`. It derives learner identity
|
|
73
73
|
from the validated enrollment. Repeated starts return existing state without
|
|
74
|
-
resetting counters, scores, completion, or timestamps.
|
|
74
|
+
resetting counters, scores, completion, or timestamps. A `not_started` row
|
|
75
|
+
(a put-back lesson) moves to `in_progress` again with its counters, score and
|
|
76
|
+
first `startedAt` kept.
|
|
77
|
+
- `putBackLesson({ enrollmentId, lessonId })` takes `in_progress` work back to
|
|
78
|
+
`not_started` for the learner or an authorized guardian under
|
|
79
|
+
`education.progress.put-back` (the host decides; own is not blocked). The
|
|
80
|
+
progress row keeps its one (enrollment, lesson) identity, so each change
|
|
81
|
+
appends an immutable `LessonProgressTransition` (from, to, kind, actor,
|
|
82
|
+
time) in the same transaction; restarting appends a `start` row when the
|
|
83
|
+
table exists. Counters, score, submissions, grades, workbook and evidence
|
|
84
|
+
rows are never touched. It returns `{ changed, progress }`: no progress or
|
|
85
|
+
already `not_started` is an idempotent no-op. `submitted`, `needs_review`,
|
|
86
|
+
`complete`, `skipped` (stored or workbook-projected) refuse with
|
|
87
|
+
`LessonPutBackError` (`.status`); completion is undone only through guardian
|
|
88
|
+
paths. A host without `lesson_progress_transitions` gets
|
|
89
|
+
`LearnerHistoryUnavailableError` from put-back; other operations still run.
|
|
90
|
+
- `setEnrollmentStatus({ enrollmentId, status, policyBasis })` moves an
|
|
91
|
+
enrollment along `ENROLLMENT_STATUS_TRANSITIONS`: planned→active,
|
|
92
|
+
active→paused|dropped, paused→active|dropped. `completed` is reached only by
|
|
93
|
+
the completion path; completed and dropped are final. The authorizer gets
|
|
94
|
+
`education.enrollment.set-status` and decides whether a learner may act on
|
|
95
|
+
their own enrollment. `policyBasis` (1-200 characters, no control characters,
|
|
96
|
+
`requirePolicyBasis`) names the host setting relied on; it and the actor are
|
|
97
|
+
appended to an immutable `EnrollmentStatusChange` with the change, as
|
|
98
|
+
`pinEnrollment` records its basis. The first activation sets `startedAt`.
|
|
99
|
+
The current status is an idempotent `{ changed: false }`; any other move
|
|
100
|
+
throws `EnrollmentStatusTransitionError` (`.from`, `.to`). Progress,
|
|
101
|
+
submissions and history never cascade. Bad input throws
|
|
102
|
+
`LearnerValidationError`; a host without `enrollment_status_changes` gets
|
|
103
|
+
`LearnerHistoryUnavailableError`.
|
|
75
104
|
- `listProgress` reads only the validated learner/enrollment and lessons still
|
|
76
105
|
visible in that enrollment's course. Inconsistent persisted links fail
|
|
77
106
|
closed or are omitted from the list.
|
|
@@ -91,7 +120,7 @@ Same-tenant membership alone never grants sibling access.
|
|
|
91
120
|
Service results are data-only views, with no persistence or database methods.
|
|
92
121
|
Extra request fields are never model patches: a learner cannot set score,
|
|
93
122
|
attempt count, or completion status through this API. Curriculum activation,
|
|
94
|
-
completion
|
|
123
|
+
enrollment completion and dashboard aggregation are not supported by this
|
|
95
124
|
base service. SubmissionService extends its owning transaction/auth boundary for
|
|
96
125
|
capture and guardian grading; see [submission contract](submission-service.md).
|
|
97
126
|
Direct model writes require trusted authority.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Question extraction from scans and imported courses
|
|
2
|
+
|
|
3
|
+
`QuestionExtractionService` (SMRT #4517, extends `QuestionAuthoringService`,
|
|
4
|
+
consumed by Hearth #80 and #94) turns ingestion proposals into structured,
|
|
5
|
+
versioned question records. Risk is high: new schema, a public contract and
|
|
6
|
+
idempotent writes. It reuses the course authorizer: preserve and apply need
|
|
7
|
+
`education.course.author`; `readExtraction`, `findExtraction` and
|
|
8
|
+
`resolveWorksheet` need `education.course.read-draft`. Operations:
|
|
9
|
+
`preserve-question-extraction`, `apply-question-extraction`,
|
|
10
|
+
`read-question-extraction`, `read-worksheet`.
|
|
11
|
+
|
|
12
|
+
## Flow
|
|
13
|
+
|
|
14
|
+
1. Receive the scan or course file through ingestion and extract it (OCR or
|
|
15
|
+
vision, with `observeAnswerRegions` for image region catalogs).
|
|
16
|
+
2. Offer `educationQuestionExtractionHandler({ extractions, request })` (from
|
|
17
|
+
`@happyvertical/smrt-education/ingestion`) in the execution catalog and
|
|
18
|
+
generate proposals. Its closed `QUESTION_EXTRACTION_ARGS_SCHEMA` is the
|
|
19
|
+
extracted question set. Keep `requireReview: true` on every policy layer.
|
|
20
|
+
Size the policy `maxBytes` and proposal output limits for whole worksheets:
|
|
21
|
+
up to 200 questions, 400 student-work entries and 1000 pages.
|
|
22
|
+
3. `preserveQuestionExtractionAnalysis({ ingestion, extractions, request,
|
|
23
|
+
itemId, expectedWorksheetVersionId?, worksheets? })` reads the completed
|
|
24
|
+
proposal, validates it against retained evidence, matches it and records a
|
|
25
|
+
`QuestionExtractionRecord`. It returns `QuestionExtractionView` for
|
|
26
|
+
extraction review. Not ready, no proposal or competing proposals throw
|
|
27
|
+
`QuestionExtractionPendingError` (`reason`); invalid output throws
|
|
28
|
+
`QuestionExtractionValidationError`. No question records are written.
|
|
29
|
+
4. The reviewer previews and decides through ingestion. The handler apply
|
|
30
|
+
calls `applyExtraction` on the ingestion transaction and returns the
|
|
31
|
+
per-question outcomes (`ExtractedQuestionResult[]`, also on `view.applied`).
|
|
32
|
+
|
|
33
|
+
Hosts without an ingestion proposal (for example a trusted bundle importer)
|
|
34
|
+
call `preserveExtraction` directly with their own grounding and extractor
|
|
35
|
+
identity, then `applyExtraction`.
|
|
36
|
+
|
|
37
|
+
## Extraction contract
|
|
38
|
+
|
|
39
|
+
- Every retained source, and each page the provider reported, has a `pages`
|
|
40
|
+
entry with a role (`exercise`, `example`, `instructions`, `cover`,
|
|
41
|
+
`answer_key`, `mixed`, `unknown`) and readability. A missing page is invalid,
|
|
42
|
+
so unreadable or unsupported content is marked, never dropped. Items that
|
|
43
|
+
cannot be read keep their key and numbering with `status` `unreadable` or
|
|
44
|
+
`unsupported` and a null prompt; other gaps go in `omissions`.
|
|
45
|
+
- What ingestion itself did not extract stays visible. A retained original with
|
|
46
|
+
no result, and pages that ingestion omitted or failed, are kept in the
|
|
47
|
+
grounding: the extractor may cite them, and any it does not cover are added as
|
|
48
|
+
`unreadable` pages with a `not_extracted` omission. Truncation and
|
|
49
|
+
`source_incomplete` warnings are carried to `extraction.sourceWarnings` and
|
|
50
|
+
the handler preview. More than 1000 pages is refused, never trimmed.
|
|
51
|
+
- Questions hold printed text only. Learner handwriting goes in `studentWork`
|
|
52
|
+
(null `observed` when illegible) and never becomes question content.
|
|
53
|
+
- Anchors cite an original `evidenceId`, a physical page the provider
|
|
54
|
+
reported (null when unknown) and a `regionId` from that evidence's
|
|
55
|
+
`observedRegions` catalog. The rect comes from the catalog only; an unknown
|
|
56
|
+
region is invalid. Coordinates are never accepted from the extractor.
|
|
57
|
+
- `origin: 'source' | 'inferred'` marks response type, keys, rubrics and points.
|
|
58
|
+
On apply, printed facts get `extracted` provenance and inferred facts get
|
|
59
|
+
`inferred`. An empty key is invalid; a missing key stays null.
|
|
60
|
+
- Diagram and media references are kept on the link with their anchor. They
|
|
61
|
+
are not question `media`, which must name a tenant asset.
|
|
62
|
+
|
|
63
|
+
## Matching
|
|
64
|
+
|
|
65
|
+
`matchExtractedQuestions(extraction, worksheets, { expectedWorksheetVersionId })`
|
|
66
|
+
is pure. A match needs a worksheet identity (the `wsv1-` id generated printables
|
|
67
|
+
print in their footer, or a host-supplied expectation), plus the printed
|
|
68
|
+
position (`1`, `1.`, `Q1`) of an item on that worksheet. Every claimant of a
|
|
69
|
+
position counts, including one that fails a layout check. Section label, a
|
|
70
|
+
source-printed response type, and choice and subpart counts must agree. Prompt
|
|
71
|
+
text is never used. A printed id that disagrees with the expectation, any layout
|
|
72
|
+
conflict, or two items claiming one position is `ambiguous` and stays
|
|
73
|
+
unattached. Examples are never matched. `resolveWorksheet` finds a worksheet by
|
|
74
|
+
id through `generated_printables`; host-supplied worksheets must be this
|
|
75
|
+
tenant's revisions. Student work gets a `questionRevisionId` only when its
|
|
76
|
+
question matched. Recording attempts and grades stays with the workbook and
|
|
77
|
+
response services.
|
|
78
|
+
|
|
79
|
+
## Idempotency
|
|
80
|
+
|
|
81
|
+
`sourceIdentity` is a digest of the source kind and the sorted retained evidence
|
|
82
|
+
hashes, never text. Preserve is idempotent per (tenant, intake item, analysis
|
|
83
|
+
attempt). A different payload for the same attempt throws
|
|
84
|
+
`CourseRequestKeyConflictError`. The payload includes the grounding, the
|
|
85
|
+
extractor identity and the expected and supplied worksheets. Apply writes questions through `createQuestion`
|
|
86
|
+
with request key `qx:<sourceIdentity>:<questionKey>`, so re-ingesting the same
|
|
87
|
+
bytes replays the same questions (`replayed`). Identical prompts under different
|
|
88
|
+
keys stay separate records. If the same source is extracted again with different
|
|
89
|
+
content, the outcome is `source_changed`: nothing is overwritten. A new scan has
|
|
90
|
+
new bytes, so it is a new source, and it links to known questions only by
|
|
91
|
+
matching. A repeated apply replays the stored `ExtractedQuestionLink` rows.
|
|
92
|
+
|
|
93
|
+
Outcomes: `created`, `replayed`, `matched` (these carry a revision),
|
|
94
|
+
`needs_review` (ambiguous), `source_changed`, `invalid_content` (fails question
|
|
95
|
+
validation), `unreadable`, `unsupported`, `not_an_exercise`.
|
|
96
|
+
|
|
97
|
+
## Models (closed, required tenant, no REST/CLI/MCP/AI, append-only)
|
|
98
|
+
|
|
99
|
+
`QuestionExtractionRecord` (`question_extractions`: unique tenant/item/attempt,
|
|
100
|
+
indexed source identity, extractor identity, validated extraction and match JSON,
|
|
101
|
+
proposal digest) and `ExtractedQuestionLink` (`extracted_question_links`: unique
|
|
102
|
+
tenant/extraction/key; outcome, question and revision ids, anchors and media).
|
|
103
|
+
Both ship through normal manifest migrations. The originals stay in ingestion
|
|
104
|
+
and are never copied. Imported material keeps its course licence; extraction
|
|
105
|
+
does not change sharing.
|
|
106
|
+
|
|
107
|
+
Not in this release: zip/bundle manifests (SMRT #4578), automatic new
|
|
108
|
+
revisions on `source_changed`, and media assets for extracted diagrams.
|
|
@@ -51,6 +51,21 @@ completion decision; grading alone leaves it in progress, including through a
|
|
|
51
51
|
plain SubmissionService instance. Existing explicit completion on the current
|
|
52
52
|
attempt is retained. The owning workbook join determines this policy, never a
|
|
53
53
|
caller flag.
|
|
54
|
+
`attestOwnLesson({ enrollmentId, lessonId, policyBasis, notes? })` is the
|
|
55
|
+
learner's own "I read it" / "I watched it" for `SELF_ATTESTABLE_LESSON_KINDS`
|
|
56
|
+
(Reading, Video) under `education.learner.attest-own`. Any other actor is
|
|
57
|
+
refused before the host authorizer; the host decides from its own setting
|
|
58
|
+
(passed as `policyBasis`, 1-200 characters) whether the learner may. It writes
|
|
59
|
+
an ungraded `Attestation` and an insert-only `LessonSelfAttestation` (learner
|
|
60
|
+
actor, basis), moves progress to `submitted` with one attempt, and creates no
|
|
61
|
+
Grade: it is not a grade and never completes the lesson. A repeat returns the
|
|
62
|
+
same attestation. Other kinds throw `OwnAttestationLessonKindError`;
|
|
63
|
+
submitted, review, complete, skipped or already-attested progress throws
|
|
64
|
+
`OwnAttestationConflictError`. Submission views carry `selfReported: true` for
|
|
65
|
+
it; a guardian confirms with `attestLesson` (which grades that attestation) or
|
|
66
|
+
`reviewSubmission`, and the guardian grade then distinguishes confirmed from
|
|
67
|
+
self-reported evidence. A host without `lesson_self_attestations` gets
|
|
68
|
+
`LearnerHistoryUnavailableError`.
|
|
54
69
|
Omitted/null score is unscored; zero is a real score. The latest guardian grade
|
|
55
70
|
wins over auto/AI/instructor proposals, and its score replaces progress's score.
|
|
56
71
|
Review history is retained. ExternalCompletion is available for trusted server
|