@things-factory/learning 10.1.261 → 10.1.263
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-client/pages/learning-course-list.d.ts +0 -4
- package/dist-client/pages/learning-course-list.js +72 -16
- package/dist-client/pages/learning-course-list.js.map +1 -1
- package/dist-client/pages/learning-enrollment-status.d.ts +3 -0
- package/dist-client/pages/learning-enrollment-status.js +25 -11
- package/dist-client/pages/learning-enrollment-status.js.map +1 -1
- package/dist-client/pages/learning-view.d.ts +10 -0
- package/dist-client/pages/learning-view.js +59 -3
- package/dist-client/pages/learning-view.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/ai-reads.d.ts +1 -0
- package/dist-server/ai-reads.js +26 -2
- package/dist-server/ai-reads.js.map +1 -1
- package/dist-server/service/course/course-query.d.ts +1 -0
- package/dist-server/service/course/course-query.js +13 -0
- package/dist-server/service/course/course-query.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-mutation.d.ts +3 -1
- package/dist-server/service/enrollment/enrollment-mutation.js +24 -6
- package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-query.d.ts +6 -1
- package/dist-server/service/enrollment/enrollment-query.js +36 -1
- package/dist-server/service/enrollment/enrollment-query.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-service.d.ts +8 -3
- package/dist-server/service/enrollment/enrollment-service.js +29 -15
- package/dist-server/service/enrollment/enrollment-service.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-status.d.ts +11 -0
- package/dist-server/service/enrollment/enrollment-status.js +42 -0
- package/dist-server/service/enrollment/enrollment-status.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-type.d.ts +7 -0
- package/dist-server/service/enrollment/enrollment-type.js +27 -1
- package/dist-server/service/enrollment/enrollment-type.js.map +1 -1
- package/dist-server/service/enrollment/index.d.ts +6 -4
- package/dist-server/service/enrollment/index.js +6 -4
- package/dist-server/service/enrollment/index.js.map +1 -1
- package/dist-server/service/enrollment/lesson-note-service.d.ts +15 -0
- package/dist-server/service/enrollment/lesson-note-service.js +86 -0
- package/dist-server/service/enrollment/lesson-note-service.js.map +1 -0
- package/dist-server/service/enrollment/lesson-note.d.ts +35 -0
- package/dist-server/service/enrollment/lesson-note.js +107 -0
- package/dist-server/service/enrollment/lesson-note.js.map +1 -0
- package/dist-server/service/enrollment/progress-rules.d.ts +4 -0
- package/dist-server/service/enrollment/progress-rules.js +7 -1
- package/dist-server/service/enrollment/progress-rules.js.map +1 -1
- package/dist-server/service/index.d.ts +4 -3
- package/dist-server/service/index.js +3 -2
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/translations/en.json +32 -16
- package/translations/es.json +19 -3
- package/translations/ja.json +19 -3
- package/translations/ko.json +33 -17
- package/translations/ms.json +19 -3
- package/translations/zh.json +19 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LearningContent = exports.LearningContentUnit = exports.LearningEnrollmentList = exports.LearningEnrollmentRow = exports.LearningAudienceInput = exports.EnrollmentStatusList = exports.EnrollmentStatusRow = exports.LearningTickResult = exports.QuizResultView = exports.QuizAttemptView = exports.QuizQuestionView = exports.QuizChoiceView = exports.LearningUnit = void 0;
|
|
3
|
+
exports.LearningContent = exports.LearningContentUnit = exports.LearningEnrollmentList = exports.LearningEnrollmentRow = exports.LearningAudienceInput = exports.EnrollmentStatusList = exports.CourseEnrollmentCounts = exports.EnrollmentStatusRow = exports.LearningTickResult = exports.QuizResultView = exports.QuizAttemptView = exports.QuizQuestionView = exports.QuizChoiceView = exports.LearningUnit = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const type_graphql_1 = require("type-graphql");
|
|
6
6
|
const auth_base_1 = require("@things-factory/auth-base");
|
|
@@ -154,6 +154,32 @@ tslib_1.__decorate([
|
|
|
154
154
|
exports.EnrollmentStatusRow = EnrollmentStatusRow = tslib_1.__decorate([
|
|
155
155
|
(0, type_graphql_1.ObjectType)({ description: 'One enrolment as the status reads it' })
|
|
156
156
|
], EnrollmentStatusRow);
|
|
157
|
+
let CourseEnrollmentCounts = class CourseEnrollmentCounts {
|
|
158
|
+
};
|
|
159
|
+
exports.CourseEnrollmentCounts = CourseEnrollmentCounts;
|
|
160
|
+
tslib_1.__decorate([
|
|
161
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Every enrolment but the cancelled.' }),
|
|
162
|
+
tslib_1.__metadata("design:type", Number)
|
|
163
|
+
], CourseEnrollmentCounts.prototype, "total", void 0);
|
|
164
|
+
tslib_1.__decorate([
|
|
165
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Not started, not past the due day.' }),
|
|
166
|
+
tslib_1.__metadata("design:type", Number)
|
|
167
|
+
], CourseEnrollmentCounts.prototype, "assigned", void 0);
|
|
168
|
+
tslib_1.__decorate([
|
|
169
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Started, not past the due day.' }),
|
|
170
|
+
tslib_1.__metadata("design:type", Number)
|
|
171
|
+
], CourseEnrollmentCounts.prototype, "inProgress", void 0);
|
|
172
|
+
tslib_1.__decorate([
|
|
173
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Completed.' }),
|
|
174
|
+
tslib_1.__metadata("design:type", Number)
|
|
175
|
+
], CourseEnrollmentCounts.prototype, "completed", void 0);
|
|
176
|
+
tslib_1.__decorate([
|
|
177
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Open past the due day.' }),
|
|
178
|
+
tslib_1.__metadata("design:type", Number)
|
|
179
|
+
], CourseEnrollmentCounts.prototype, "overdue", void 0);
|
|
180
|
+
exports.CourseEnrollmentCounts = CourseEnrollmentCounts = tslib_1.__decorate([
|
|
181
|
+
(0, type_graphql_1.ObjectType)({ description: "Where a course's enrolments stand - cancelled ones left out" })
|
|
182
|
+
], CourseEnrollmentCounts);
|
|
157
183
|
let EnrollmentStatusList = class EnrollmentStatusList {
|
|
158
184
|
};
|
|
159
185
|
exports.EnrollmentStatusList = EnrollmentStatusList;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrollment-type.js","sourceRoot":"","sources":["../../../server/service/enrollment/enrollment-type.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, InputType, Int, ObjectType } from 'type-graphql'\n\nimport { User } from '@things-factory/auth-base'\n\nimport { Course } from '../course/course'\nimport { CourseUnit } from '../course/course-unit'\nimport { Enrollment } from './enrollment'\nimport { UnitProgress } from './unit-progress'\n\n@ObjectType({ description: 'A unit as its learner sees it - with how far they went through it' })\nexport class LearningUnit {\n @Field(type => CourseUnit, { description: 'The unit.' })\n unit: CourseUnit\n\n @Field(type => UnitProgress, { nullable: true, description: 'How far the learner went - none before they started it.' })\n progress?: UnitProgress | null\n\n @Field({ description: 'Whether it counts toward completion - a link does not.' })\n counts: boolean\n}\n\n@ObjectType({ description: 'A choice as a learner is drawn it - never whether it is right' })\nexport class QuizChoiceView {\n @Field({ description: 'The choice id.' })\n id: string\n\n @Field({ description: 'What it says.' })\n text: string\n}\n\n@ObjectType({ description: 'A question as a learner is drawn it - its choices in the order shown, no answer' })\nexport class QuizQuestionView {\n @Field({ description: 'The question id.' })\n id: string\n\n @Field({ description: 'SINGLE, MULTIPLE or TRUE_FALSE.' })\n kind: string\n\n @Field({ description: 'What it asks.' })\n text: string\n\n @Field(type => [QuizChoiceView], { description: 'Its choices, shuffled.' })\n choices: QuizChoiceView[]\n}\n\n@ObjectType({ description: 'An attempt as a learner is drawn it' })\nexport class QuizAttemptView {\n @Field({ description: 'The attempt id - its answers are sent with it.' })\n attemptId: string\n\n @Field(type => [QuizQuestionView], { description: 'The questions drawn, shuffled.' })\n questions: QuizQuestionView[]\n}\n\n@ObjectType({ description: \"An attempt's result - the score and whether it passed; never the right answers\" })\nexport class QuizResultView {\n @Field(type => Int, { description: 'The score, out of 100.' })\n score: number\n\n @Field({ description: 'Whether it passed.' })\n passed: boolean\n\n @Field(type => Int, { nullable: true, description: 'Attempts left - no limit when empty.' })\n attemptsLeft?: number | null\n\n @Field(type => Enrollment, { description: 'The enrolment after it - completed when nothing else is missing.' })\n enrollment: Enrollment\n}\n\n@ObjectType({ description: \"A tick's result - the unit's progress and the enrolment after it\" })\nexport class LearningTickResult {\n @Field(type => Enrollment, { description: 'The enrolment.' })\n enrollment: Enrollment\n\n @Field(type => UnitProgress, { description: \"The unit's progress.\" })\n progress: UnitProgress\n}\n\n@ObjectType({ description: 'One enrolment as the status reads it' })\nexport class EnrollmentStatusRow {\n @Field({ description: 'The enrolment id.' })\n id: string\n\n @Field(type => Enrollment, { description: 'The enrolment - for a department head its status and days only.' })\n enrollment: Enrollment\n\n @Field(type => User, { nullable: true, description: 'Who takes it.' })\n user?: User | null\n\n @Field(type => Course, { nullable: true, description: 'The course.' })\n course?: Course | null\n\n @Field({ nullable: true, description: 'Their department.' })\n departmentName?: string | null\n\n @Field({ description: 'ASSIGNED, IN_PROGRESS, COMPLETED, CANCELLED, or OVERDUE for an open one past its due day.' })\n state: string\n\n @Field(type => Int, { description: 'The units that count.' })\n units: number\n\n @Field(type => Int, { description: 'The units done.' })\n unitsDone: number\n\n @Field(type => Int, { nullable: true, description: 'The version taken.' })\n versionNumber?: number | null\n}\n\n@ObjectType({ description: 'A page of enrolments as the status reads them' })\nexport class EnrollmentStatusList {\n @Field(type => [EnrollmentStatusRow], { description: 'The enrolments of the page.' })\n items: EnrollmentStatusRow[]\n\n @Field(type => Int, { description: 'How many there are under the conditions.' })\n total: number\n}\n\n@InputType({ description: 'One target of an audience - the domain, a department (with those under it), a role or a person' })\nexport class LearningAudienceInput {\n @Field({ description: 'DOMAIN | DEPARTMENT | ROLE | USER.' })\n scope: string\n\n @Field({ description: 'The id of the domain, department, role or user.' })\n targetId: string\n\n @Field({ nullable: true, description: 'Its name, as shown.' })\n targetName?: string\n}\n\n@ObjectType({ description: 'An enrolment as the assistant lists it' })\nexport class LearningEnrollmentRow {\n @Field({ description: 'The enrolment id.' }) id: string\n @Field({ description: 'Who takes it.' }) learner: string\n @Field({ nullable: true, description: 'Their department.' }) departmentName?: string | null\n @Field({ description: 'The course.' }) course: string\n @Field({ description: 'ASSIGNED, IN_PROGRESS, COMPLETED or OVERDUE.' }) state: string\n @Field({ nullable: true, description: 'YYYY-MM-DD it is due.' }) dueOn?: string | null\n @Field(type => Int, { nullable: true, description: 'Days to the due day - negative past it.' }) daysLeft?: number | null\n @Field({ nullable: true, description: 'When it was started.' }) startedAt?: Date | null\n @Field({ nullable: true, description: 'When it was completed.' }) completedAt?: Date | null\n @Field(type => Int, { description: 'Units done.' }) unitsDone: number\n @Field(type => Int, { description: 'Units that count.' }) units: number\n}\n\n@ObjectType({ description: 'A page of enrolments as the assistant lists them' })\nexport class LearningEnrollmentList {\n @Field(type => [LearningEnrollmentRow], { description: 'The enrolments.' }) items: LearningEnrollmentRow[]\n @Field(type => Int, { description: 'How many there are.' }) total: number\n}\n\n@ObjectType({ description: 'A unit as the assistant reads it' })\nexport class LearningContentUnit {\n @Field(type => Int, { description: 'Its place.' }) seq: number\n @Field({ description: 'VIDEO, PDF, MARKDOWN or LINK.' }) kind: string\n @Field({ description: 'Its name.' }) name: string\n @Field({ nullable: true, description: 'The text of a MARKDOWN unit, the address of a LINK.' }) text?: string | null\n}\n\n@ObjectType({ description: 'The units of one course version, as the assistant reads them' })\nexport class LearningContent {\n @Field({ description: 'The course.' }) course: string\n @Field(type => Int, { description: 'The version number.' }) version: number\n @Field(type => [LearningContentUnit], { description: 'Its units.' }) units: LearningContentUnit[]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"enrollment-type.js","sourceRoot":"","sources":["../../../server/service/enrollment/enrollment-type.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, InputType, Int, ObjectType } from 'type-graphql'\n\nimport { User } from '@things-factory/auth-base'\n\nimport { Course } from '../course/course'\nimport { CourseUnit } from '../course/course-unit'\nimport { Enrollment } from './enrollment'\nimport { UnitProgress } from './unit-progress'\n\n@ObjectType({ description: 'A unit as its learner sees it - with how far they went through it' })\nexport class LearningUnit {\n @Field(type => CourseUnit, { description: 'The unit.' })\n unit: CourseUnit\n\n @Field(type => UnitProgress, { nullable: true, description: 'How far the learner went - none before they started it.' })\n progress?: UnitProgress | null\n\n @Field({ description: 'Whether it counts toward completion - a link does not.' })\n counts: boolean\n}\n\n@ObjectType({ description: 'A choice as a learner is drawn it - never whether it is right' })\nexport class QuizChoiceView {\n @Field({ description: 'The choice id.' })\n id: string\n\n @Field({ description: 'What it says.' })\n text: string\n}\n\n@ObjectType({ description: 'A question as a learner is drawn it - its choices in the order shown, no answer' })\nexport class QuizQuestionView {\n @Field({ description: 'The question id.' })\n id: string\n\n @Field({ description: 'SINGLE, MULTIPLE or TRUE_FALSE.' })\n kind: string\n\n @Field({ description: 'What it asks.' })\n text: string\n\n @Field(type => [QuizChoiceView], { description: 'Its choices, shuffled.' })\n choices: QuizChoiceView[]\n}\n\n@ObjectType({ description: 'An attempt as a learner is drawn it' })\nexport class QuizAttemptView {\n @Field({ description: 'The attempt id - its answers are sent with it.' })\n attemptId: string\n\n @Field(type => [QuizQuestionView], { description: 'The questions drawn, shuffled.' })\n questions: QuizQuestionView[]\n}\n\n@ObjectType({ description: \"An attempt's result - the score and whether it passed; never the right answers\" })\nexport class QuizResultView {\n @Field(type => Int, { description: 'The score, out of 100.' })\n score: number\n\n @Field({ description: 'Whether it passed.' })\n passed: boolean\n\n @Field(type => Int, { nullable: true, description: 'Attempts left - no limit when empty.' })\n attemptsLeft?: number | null\n\n @Field(type => Enrollment, { description: 'The enrolment after it - completed when nothing else is missing.' })\n enrollment: Enrollment\n}\n\n@ObjectType({ description: \"A tick's result - the unit's progress and the enrolment after it\" })\nexport class LearningTickResult {\n @Field(type => Enrollment, { description: 'The enrolment.' })\n enrollment: Enrollment\n\n @Field(type => UnitProgress, { description: \"The unit's progress.\" })\n progress: UnitProgress\n}\n\n@ObjectType({ description: 'One enrolment as the status reads it' })\nexport class EnrollmentStatusRow {\n @Field({ description: 'The enrolment id.' })\n id: string\n\n @Field(type => Enrollment, { description: 'The enrolment - for a department head its status and days only.' })\n enrollment: Enrollment\n\n @Field(type => User, { nullable: true, description: 'Who takes it.' })\n user?: User | null\n\n @Field(type => Course, { nullable: true, description: 'The course.' })\n course?: Course | null\n\n @Field({ nullable: true, description: 'Their department.' })\n departmentName?: string | null\n\n @Field({ description: 'ASSIGNED, IN_PROGRESS, COMPLETED, CANCELLED, or OVERDUE for an open one past its due day.' })\n state: string\n\n @Field(type => Int, { description: 'The units that count.' })\n units: number\n\n @Field(type => Int, { description: 'The units done.' })\n unitsDone: number\n\n @Field(type => Int, { nullable: true, description: 'The version taken.' })\n versionNumber?: number | null\n}\n\n@ObjectType({ description: \"Where a course's enrolments stand - cancelled ones left out\" })\nexport class CourseEnrollmentCounts {\n @Field(type => Int, { description: 'Every enrolment but the cancelled.' })\n total: number\n\n @Field(type => Int, { description: 'Not started, not past the due day.' })\n assigned: number\n\n @Field(type => Int, { description: 'Started, not past the due day.' })\n inProgress: number\n\n @Field(type => Int, { description: 'Completed.' })\n completed: number\n\n @Field(type => Int, { description: 'Open past the due day.' })\n overdue: number\n}\n\n@ObjectType({ description: 'A page of enrolments as the status reads them' })\nexport class EnrollmentStatusList {\n @Field(type => [EnrollmentStatusRow], { description: 'The enrolments of the page.' })\n items: EnrollmentStatusRow[]\n\n @Field(type => Int, { description: 'How many there are under the conditions.' })\n total: number\n}\n\n@InputType({ description: 'One target of an audience - the domain, a department (with those under it), a role or a person' })\nexport class LearningAudienceInput {\n @Field({ description: 'DOMAIN | DEPARTMENT | ROLE | USER.' })\n scope: string\n\n @Field({ description: 'The id of the domain, department, role or user.' })\n targetId: string\n\n @Field({ nullable: true, description: 'Its name, as shown.' })\n targetName?: string\n}\n\n@ObjectType({ description: 'An enrolment as the assistant lists it' })\nexport class LearningEnrollmentRow {\n @Field({ description: 'The enrolment id.' }) id: string\n @Field({ description: 'Who takes it.' }) learner: string\n @Field({ nullable: true, description: 'Their department.' }) departmentName?: string | null\n @Field({ description: 'The course.' }) course: string\n @Field({ description: 'ASSIGNED, IN_PROGRESS, COMPLETED or OVERDUE.' }) state: string\n @Field({ nullable: true, description: 'YYYY-MM-DD it is due.' }) dueOn?: string | null\n @Field(type => Int, { nullable: true, description: 'Days to the due day - negative past it.' }) daysLeft?: number | null\n @Field({ nullable: true, description: 'When it was started.' }) startedAt?: Date | null\n @Field({ nullable: true, description: 'When it was completed.' }) completedAt?: Date | null\n @Field(type => Int, { description: 'Units done.' }) unitsDone: number\n @Field(type => Int, { description: 'Units that count.' }) units: number\n}\n\n@ObjectType({ description: 'A page of enrolments as the assistant lists them' })\nexport class LearningEnrollmentList {\n @Field(type => [LearningEnrollmentRow], { description: 'The enrolments.' }) items: LearningEnrollmentRow[]\n @Field(type => Int, { description: 'How many there are.' }) total: number\n}\n\n@ObjectType({ description: 'A unit as the assistant reads it' })\nexport class LearningContentUnit {\n @Field(type => Int, { description: 'Its place.' }) seq: number\n @Field({ description: 'VIDEO, PDF, MARKDOWN or LINK.' }) kind: string\n @Field({ description: 'Its name.' }) name: string\n @Field({ nullable: true, description: 'The text of a MARKDOWN unit, the address of a LINK.' }) text?: string | null\n}\n\n@ObjectType({ description: 'The units of one course version, as the assistant reads them' })\nexport class LearningContent {\n @Field({ description: 'The course.' }) course: string\n @Field(type => Int, { description: 'The version number.' }) version: number\n @Field(type => [LearningContentUnit], { description: 'Its units.' }) units: LearningContentUnit[]\n}\n"]}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { Enrollment } from './enrollment';
|
|
2
2
|
import { QuizAttempt } from './quiz-attempt';
|
|
3
3
|
import { UnitProgress } from './unit-progress';
|
|
4
|
+
import { LessonNote } from './lesson-note';
|
|
4
5
|
import { EnrollmentMutation } from './enrollment-mutation';
|
|
5
|
-
import { EnrollmentQuery } from './enrollment-query';
|
|
6
|
+
import { CourseEnrollmentFields, EnrollmentQuery } from './enrollment-query';
|
|
6
7
|
export * from './enrollment';
|
|
7
8
|
export * from './quiz-attempt';
|
|
8
9
|
export * from './unit-progress';
|
|
10
|
+
export * from './lesson-note';
|
|
9
11
|
export * from './learning-outcome';
|
|
10
|
-
export {
|
|
12
|
+
export { cancelEnrollments, DIRECT_SOURCE_KIND, enroll, learningAddressOf, withdraw } from './enrollment-service';
|
|
11
13
|
export type { LearningSource } from './enrollment-service';
|
|
12
14
|
export { LEARNING_CERTIFICATE_KIND, LEARNING_REGISTER_KIND } from './learning-documents';
|
|
13
|
-
export declare const entities: (typeof Enrollment | typeof QuizAttempt | typeof UnitProgress)[];
|
|
14
|
-
export declare const resolvers: (typeof EnrollmentMutation | typeof EnrollmentQuery)[];
|
|
15
|
+
export declare const entities: (typeof Enrollment | typeof QuizAttempt | typeof UnitProgress | typeof LessonNote)[];
|
|
16
|
+
export declare const resolvers: (typeof EnrollmentMutation | typeof EnrollmentQuery | typeof CourseEnrollmentFields)[];
|
|
15
17
|
export declare const subscribers: any[];
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.subscribers = exports.resolvers = exports.entities = exports.LEARNING_REGISTER_KIND = exports.LEARNING_CERTIFICATE_KIND = exports.withdraw = exports.learningAddressOf = exports.enroll = exports.DIRECT_SOURCE_KIND = exports.
|
|
3
|
+
exports.subscribers = exports.resolvers = exports.entities = exports.LEARNING_REGISTER_KIND = exports.LEARNING_CERTIFICATE_KIND = exports.withdraw = exports.learningAddressOf = exports.enroll = exports.DIRECT_SOURCE_KIND = exports.cancelEnrollments = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const enrollment_1 = require("./enrollment");
|
|
6
6
|
const quiz_attempt_1 = require("./quiz-attempt");
|
|
7
7
|
const unit_progress_1 = require("./unit-progress");
|
|
8
|
+
const lesson_note_1 = require("./lesson-note");
|
|
8
9
|
const enrollment_mutation_1 = require("./enrollment-mutation");
|
|
9
10
|
const enrollment_query_1 = require("./enrollment-query");
|
|
10
11
|
tslib_1.__exportStar(require("./enrollment"), exports);
|
|
11
12
|
tslib_1.__exportStar(require("./quiz-attempt"), exports);
|
|
12
13
|
tslib_1.__exportStar(require("./unit-progress"), exports);
|
|
14
|
+
tslib_1.__exportStar(require("./lesson-note"), exports);
|
|
13
15
|
tslib_1.__exportStar(require("./learning-outcome"), exports);
|
|
14
16
|
var enrollment_service_1 = require("./enrollment-service");
|
|
15
|
-
Object.defineProperty(exports, "
|
|
17
|
+
Object.defineProperty(exports, "cancelEnrollments", { enumerable: true, get: function () { return enrollment_service_1.cancelEnrollments; } });
|
|
16
18
|
Object.defineProperty(exports, "DIRECT_SOURCE_KIND", { enumerable: true, get: function () { return enrollment_service_1.DIRECT_SOURCE_KIND; } });
|
|
17
19
|
Object.defineProperty(exports, "enroll", { enumerable: true, get: function () { return enrollment_service_1.enroll; } });
|
|
18
20
|
Object.defineProperty(exports, "learningAddressOf", { enumerable: true, get: function () { return enrollment_service_1.learningAddressOf; } });
|
|
@@ -20,7 +22,7 @@ Object.defineProperty(exports, "withdraw", { enumerable: true, get: function ()
|
|
|
20
22
|
var learning_documents_1 = require("./learning-documents");
|
|
21
23
|
Object.defineProperty(exports, "LEARNING_CERTIFICATE_KIND", { enumerable: true, get: function () { return learning_documents_1.LEARNING_CERTIFICATE_KIND; } });
|
|
22
24
|
Object.defineProperty(exports, "LEARNING_REGISTER_KIND", { enumerable: true, get: function () { return learning_documents_1.LEARNING_REGISTER_KIND; } });
|
|
23
|
-
exports.entities = [enrollment_1.Enrollment, unit_progress_1.UnitProgress, quiz_attempt_1.QuizAttempt];
|
|
24
|
-
exports.resolvers = [enrollment_query_1.EnrollmentQuery, enrollment_mutation_1.EnrollmentMutation];
|
|
25
|
+
exports.entities = [enrollment_1.Enrollment, unit_progress_1.UnitProgress, quiz_attempt_1.QuizAttempt, lesson_note_1.LessonNote];
|
|
26
|
+
exports.resolvers = [enrollment_query_1.EnrollmentQuery, enrollment_query_1.CourseEnrollmentFields, enrollment_mutation_1.EnrollmentMutation];
|
|
25
27
|
exports.subscribers = [];
|
|
26
28
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/enrollment/index.ts"],"names":[],"mappings":";;;;AAAA,6CAAyC;AACzC,iDAA4C;AAC5C,mDAA8C;AAC9C,+DAA0D;AAC1D,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/enrollment/index.ts"],"names":[],"mappings":";;;;AAAA,6CAAyC;AACzC,iDAA4C;AAC5C,mDAA8C;AAC9C,+CAA0C;AAC1C,+DAA0D;AAC1D,yDAA4E;AAE5E,uDAA4B;AAC5B,yDAA8B;AAC9B,0DAA+B;AAC/B,wDAA6B;AAC7B,6DAAkC;AAClC,2DAAiH;AAAxG,uHAAA,iBAAiB,OAAA;AAAE,wHAAA,kBAAkB,OAAA;AAAE,4GAAA,MAAM,OAAA;AAAE,uHAAA,iBAAiB,OAAA;AAAE,8GAAA,QAAQ,OAAA;AAEnF,2DAAwF;AAA/E,+HAAA,yBAAyB,OAAA;AAAE,4HAAA,sBAAsB,OAAA;AAE7C,QAAA,QAAQ,GAAG,CAAC,uBAAU,EAAE,4BAAY,EAAE,0BAAW,EAAE,wBAAU,CAAC,CAAA;AAC9D,QAAA,SAAS,GAAG,CAAC,kCAAe,EAAE,yCAAsB,EAAE,wCAAkB,CAAC,CAAA;AACzE,QAAA,WAAW,GAAG,EAAE,CAAA","sourcesContent":["import { Enrollment } from './enrollment'\nimport { QuizAttempt } from './quiz-attempt'\nimport { UnitProgress } from './unit-progress'\nimport { LessonNote } from './lesson-note'\nimport { EnrollmentMutation } from './enrollment-mutation'\nimport { CourseEnrollmentFields, EnrollmentQuery } from './enrollment-query'\n\nexport * from './enrollment'\nexport * from './quiz-attempt'\nexport * from './unit-progress'\nexport * from './lesson-note'\nexport * from './learning-outcome'\nexport { cancelEnrollments, DIRECT_SOURCE_KIND, enroll, learningAddressOf, withdraw } from './enrollment-service'\nexport type { LearningSource } from './enrollment-service'\nexport { LEARNING_CERTIFICATE_KIND, LEARNING_REGISTER_KIND } from './learning-documents'\n\nexport const entities = [Enrollment, UnitProgress, QuizAttempt, LessonNote]\nexport const resolvers = [EnrollmentQuery, CourseEnrollmentFields, EnrollmentMutation]\nexport const subscribers = []\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LessonNote, LessonNoteStroke } from './lesson-note';
|
|
2
|
+
/** What one page's note may hold (ADR-0194 amendment 2-4). */
|
|
3
|
+
export declare const NOTE_LIMITS: {
|
|
4
|
+
strokes: number;
|
|
5
|
+
points: number;
|
|
6
|
+
};
|
|
7
|
+
/** The strokes as they are kept - each checked; anything else is refused, never mended. */
|
|
8
|
+
export declare function noteStrokesOf(strokes: unknown): LessonNoteStroke[];
|
|
9
|
+
/** The learner's notes on a unit of their own enrolment, by page - none before it started. */
|
|
10
|
+
export declare function myLessonNotes(enrollmentId: string, unitId: string, context: any): Promise<LessonNote[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Keeps a page's strokes - the whole page each time, as the screen has it after a stroke. The page is one of the unit's;
|
|
13
|
+
* the enrolment the learner's own and not cancelled. A completed enrolment still takes notes (amendment 2-7).
|
|
14
|
+
*/
|
|
15
|
+
export declare function saveLessonNote(enrollmentId: string, unitId: string, page: number, strokes: unknown, context: any): Promise<LessonNote>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NOTE_LIMITS = void 0;
|
|
4
|
+
exports.noteStrokesOf = noteStrokesOf;
|
|
5
|
+
exports.myLessonNotes = myLessonNotes;
|
|
6
|
+
exports.saveLessonNote = saveLessonNote;
|
|
7
|
+
const shell_1 = require("@things-factory/shell");
|
|
8
|
+
const course_service_1 = require("../course/course-service");
|
|
9
|
+
const course_unit_1 = require("../course/course-unit");
|
|
10
|
+
const enrollment_1 = require("./enrollment");
|
|
11
|
+
const enrollment_service_1 = require("./enrollment-service");
|
|
12
|
+
const lesson_note_1 = require("./lesson-note");
|
|
13
|
+
/** What one page's note may hold (ADR-0194 amendment 2-4). */
|
|
14
|
+
exports.NOTE_LIMITS = { strokes: 1000, points: 50000 };
|
|
15
|
+
const TOOLS = ['pen', 'highlighter'];
|
|
16
|
+
const COLOR = /^#[0-9a-f]{6}$/i;
|
|
17
|
+
/** The strokes as they are kept - each checked; anything else is refused, never mended. */
|
|
18
|
+
function noteStrokesOf(strokes) {
|
|
19
|
+
if (!Array.isArray(strokes))
|
|
20
|
+
throw new shell_1.Refusal('learning-note-invalid', {}, 'the strokes are not a list');
|
|
21
|
+
if (strokes.length > exports.NOTE_LIMITS.strokes)
|
|
22
|
+
throw new shell_1.Refusal('learning-note-too-large', exports.NOTE_LIMITS, `more than ${exports.NOTE_LIMITS.strokes} strokes on a page`);
|
|
23
|
+
let points = 0;
|
|
24
|
+
const kept = strokes.map((one) => {
|
|
25
|
+
const fine = one &&
|
|
26
|
+
TOOLS.includes(one.tool) &&
|
|
27
|
+
typeof one.color === 'string' &&
|
|
28
|
+
COLOR.test(one.color) &&
|
|
29
|
+
typeof one.width === 'number' &&
|
|
30
|
+
one.width > 0 &&
|
|
31
|
+
one.width <= 0.05 &&
|
|
32
|
+
Array.isArray(one.points) &&
|
|
33
|
+
one.points.length >= 2 &&
|
|
34
|
+
one.points.length % 2 === 0 &&
|
|
35
|
+
one.points.every((value) => typeof value === 'number' && value >= 0 && value <= 1);
|
|
36
|
+
if (!fine)
|
|
37
|
+
throw new shell_1.Refusal('learning-note-invalid', {}, 'a stroke is not one the page writes');
|
|
38
|
+
points += one.points.length / 2;
|
|
39
|
+
return { tool: one.tool, color: one.color, width: one.width, points: one.points };
|
|
40
|
+
});
|
|
41
|
+
if (points > exports.NOTE_LIMITS.points)
|
|
42
|
+
throw new shell_1.Refusal('learning-note-too-large', exports.NOTE_LIMITS, `more than ${exports.NOTE_LIMITS.points} points on a page`);
|
|
43
|
+
return kept;
|
|
44
|
+
}
|
|
45
|
+
/** The PDF unit of the learner's own enrolment, its version's - a cancelled enrolment takes no notes. */
|
|
46
|
+
async function notedUnitOf(tx, context, enrollmentId, unitId) {
|
|
47
|
+
const enrollment = await (0, enrollment_service_1.myEnrollmentOf)(context, enrollmentId, tx);
|
|
48
|
+
if (!enrollment.versionId)
|
|
49
|
+
throw new shell_1.Refusal('learning-note-not-started', {}, 'the enrolment has not started');
|
|
50
|
+
const unit = await (0, enrollment_service_1.unitOfVersion)(tx, { id: enrollment.versionId }, unitId);
|
|
51
|
+
if (unit.kind !== course_unit_1.CourseUnitKind.PDF)
|
|
52
|
+
throw new shell_1.Refusal('learning-note-pdf-only', {}, 'notes are written over the pages of a PDF');
|
|
53
|
+
return { enrollment, unit };
|
|
54
|
+
}
|
|
55
|
+
/** The learner's notes on a unit of their own enrolment, by page - none before it started. */
|
|
56
|
+
async function myLessonNotes(enrollmentId, unitId, context) {
|
|
57
|
+
const tx = (0, course_service_1.learningManagerOf)(context);
|
|
58
|
+
/* Not started yet - the course opens and starts it right after this read: nothing is written yet (2026-11-08). */
|
|
59
|
+
if (!(await (0, enrollment_service_1.myEnrollmentOf)(context, enrollmentId, tx)).versionId)
|
|
60
|
+
return [];
|
|
61
|
+
const { enrollment, unit } = await notedUnitOf(tx, context, enrollmentId, unitId);
|
|
62
|
+
return await tx
|
|
63
|
+
.getRepository(lesson_note_1.LessonNote)
|
|
64
|
+
.find({ where: { enrollment: { id: enrollment.id }, unit: { id: unit.id } }, order: { page: 'ASC' } });
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Keeps a page's strokes - the whole page each time, as the screen has it after a stroke. The page is one of the unit's;
|
|
68
|
+
* the enrolment the learner's own and not cancelled. A completed enrolment still takes notes (amendment 2-7).
|
|
69
|
+
*/
|
|
70
|
+
async function saveLessonNote(enrollmentId, unitId, page, strokes, context) {
|
|
71
|
+
const { domain, user, tx } = context.state;
|
|
72
|
+
const { enrollment, unit } = await notedUnitOf(tx, context, enrollmentId, unitId);
|
|
73
|
+
if (enrollment.status === enrollment_1.EnrollmentStatus.CANCELLED)
|
|
74
|
+
throw new shell_1.Refusal('learning-enrollment-cancelled', {}, 'the enrolment is cancelled');
|
|
75
|
+
if (!Number.isInteger(page) || page < 1 || (unit.pageCount && page > unit.pageCount))
|
|
76
|
+
throw new shell_1.Refusal('learning-note-page', { pages: unit.pageCount ?? 0 }, `no page ${page} in the document`);
|
|
77
|
+
const kept = noteStrokesOf(strokes);
|
|
78
|
+
const repository = tx.getRepository(lesson_note_1.LessonNote);
|
|
79
|
+
const row = await repository.findOne({
|
|
80
|
+
where: { enrollment: { id: enrollment.id }, unit: { id: unit.id }, page }
|
|
81
|
+
});
|
|
82
|
+
if (row)
|
|
83
|
+
return await repository.save({ ...row, strokes: kept, updater: user });
|
|
84
|
+
return await repository.save({ domain, enrollment, unit, page, strokes: kept, creator: user, updater: user });
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=lesson-note-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lesson-note-service.js","sourceRoot":"","sources":["../../../server/service/enrollment/lesson-note-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager } from 'typeorm'\n\nimport { Refusal } from '@things-factory/shell'\n\nimport { learningManagerOf } from '../course/course-service'\nimport { CourseUnitKind } from '../course/course-unit'\nimport { EnrollmentStatus } from './enrollment'\nimport { myEnrollmentOf, unitOfVersion } from './enrollment-service'\nimport { LessonNote, LessonNoteStroke } from './lesson-note'\n\n/** What one page's note may hold (ADR-0194 amendment 2-4). */\nexport const NOTE_LIMITS = { strokes: 1000, points: 50000 }\n\nconst TOOLS = ['pen', 'highlighter']\nconst COLOR = /^#[0-9a-f]{6}$/i\n\n/** The strokes as they are kept - each checked; anything else is refused, never mended. */\nexport function noteStrokesOf(strokes: unknown): LessonNoteStroke[] {\n if (!Array.isArray(strokes)) throw new Refusal('learning-note-invalid', {}, 'the strokes are not a list')\n if (strokes.length > NOTE_LIMITS.strokes)\n throw new Refusal('learning-note-too-large', NOTE_LIMITS, `more than ${NOTE_LIMITS.strokes} strokes on a page`)\n let points = 0\n const kept = strokes.map((one: any) => {\n const fine =\n one &&\n TOOLS.includes(one.tool) &&\n typeof one.color === 'string' &&\n COLOR.test(one.color) &&\n typeof one.width === 'number' &&\n one.width > 0 &&\n one.width <= 0.05 &&\n Array.isArray(one.points) &&\n one.points.length >= 2 &&\n one.points.length % 2 === 0 &&\n one.points.every((value: unknown) => typeof value === 'number' && value >= 0 && value <= 1)\n if (!fine) throw new Refusal('learning-note-invalid', {}, 'a stroke is not one the page writes')\n points += one.points.length / 2\n return { tool: one.tool, color: one.color, width: one.width, points: one.points }\n })\n if (points > NOTE_LIMITS.points)\n throw new Refusal('learning-note-too-large', NOTE_LIMITS, `more than ${NOTE_LIMITS.points} points on a page`)\n return kept\n}\n\n/** The PDF unit of the learner's own enrolment, its version's - a cancelled enrolment takes no notes. */\nasync function notedUnitOf(tx: EntityManager, context: any, enrollmentId: string, unitId: string) {\n const enrollment = await myEnrollmentOf(context, enrollmentId, tx)\n if (!enrollment.versionId) throw new Refusal('learning-note-not-started', {}, 'the enrolment has not started')\n const unit = await unitOfVersion(tx, { id: enrollment.versionId }, unitId)\n if (unit.kind !== CourseUnitKind.PDF)\n throw new Refusal('learning-note-pdf-only', {}, 'notes are written over the pages of a PDF')\n return { enrollment, unit }\n}\n\n/** The learner's notes on a unit of their own enrolment, by page - none before it started. */\nexport async function myLessonNotes(enrollmentId: string, unitId: string, context: any): Promise<LessonNote[]> {\n const tx = learningManagerOf(context)\n /* Not started yet - the course opens and starts it right after this read: nothing is written yet (2026-11-08). */\n if (!(await myEnrollmentOf(context, enrollmentId, tx)).versionId) return []\n const { enrollment, unit } = await notedUnitOf(tx, context, enrollmentId, unitId)\n return await tx\n .getRepository(LessonNote)\n .find({ where: { enrollment: { id: enrollment.id }, unit: { id: unit.id } } as any, order: { page: 'ASC' } })\n}\n\n/**\n * Keeps a page's strokes - the whole page each time, as the screen has it after a stroke. The page is one of the unit's;\n * the enrolment the learner's own and not cancelled. A completed enrolment still takes notes (amendment 2-7).\n */\nexport async function saveLessonNote(\n enrollmentId: string,\n unitId: string,\n page: number,\n strokes: unknown,\n context: any\n): Promise<LessonNote> {\n const { domain, user, tx } = context.state\n const { enrollment, unit } = await notedUnitOf(tx, context, enrollmentId, unitId)\n if (enrollment.status === EnrollmentStatus.CANCELLED)\n throw new Refusal('learning-enrollment-cancelled', {}, 'the enrolment is cancelled')\n if (!Number.isInteger(page) || page < 1 || (unit.pageCount && page > unit.pageCount))\n throw new Refusal('learning-note-page', { pages: unit.pageCount ?? 0 }, `no page ${page} in the document`)\n const kept = noteStrokesOf(strokes)\n const repository = (tx as EntityManager).getRepository(LessonNote)\n const row = await repository.findOne({\n where: { enrollment: { id: enrollment.id }, unit: { id: unit.id }, page } as any\n })\n if (row) return await repository.save({ ...row, strokes: kept, updater: user } as any)\n return await repository.save({ domain, enrollment, unit, page, strokes: kept, creator: user, updater: user } as any)\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { User } from '@things-factory/auth-base';
|
|
2
|
+
import { Domain } from '@things-factory/shell';
|
|
3
|
+
import { CourseUnit } from '../course/course-unit';
|
|
4
|
+
import { Enrollment } from './enrollment';
|
|
5
|
+
/**
|
|
6
|
+
* 노트 (ADR-0194 amendment 2-3~7) - what a learner wrote over one page of a PDF unit in one enrolment: pen and
|
|
7
|
+
* highlighter strokes, their points as fractions of the page. One per enrolment, unit and page. The learner's own -
|
|
8
|
+
* read and written by them alone, never by a manager or a department head, and no part of a completion or its evidence.
|
|
9
|
+
* An enrolment takes one version, so a note stays on the page it was written on; a new version is a new enrolment.
|
|
10
|
+
*/
|
|
11
|
+
export declare class LessonNote {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
domain?: Domain;
|
|
14
|
+
domainId?: string;
|
|
15
|
+
enrollment?: Enrollment;
|
|
16
|
+
enrollmentId?: string;
|
|
17
|
+
unit?: CourseUnit;
|
|
18
|
+
unitId?: string;
|
|
19
|
+
page: number;
|
|
20
|
+
strokes: LessonNoteStroke[];
|
|
21
|
+
createdAt?: Date;
|
|
22
|
+
updatedAt?: Date;
|
|
23
|
+
deletedAt?: Date;
|
|
24
|
+
creator?: User;
|
|
25
|
+
creatorId?: string;
|
|
26
|
+
updater?: User;
|
|
27
|
+
updaterId?: string;
|
|
28
|
+
}
|
|
29
|
+
/** One stroke of a note - `@operato/learning`'s InkStroke. */
|
|
30
|
+
export interface LessonNoteStroke {
|
|
31
|
+
tool: 'pen' | 'highlighter';
|
|
32
|
+
color: string;
|
|
33
|
+
width: number;
|
|
34
|
+
points: number[];
|
|
35
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LessonNote = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const typeorm_1 = require("typeorm");
|
|
7
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
8
|
+
const shell_1 = require("@things-factory/shell");
|
|
9
|
+
const course_unit_1 = require("../course/course-unit");
|
|
10
|
+
const enrollment_1 = require("./enrollment");
|
|
11
|
+
/**
|
|
12
|
+
* 노트 (ADR-0194 amendment 2-3~7) - what a learner wrote over one page of a PDF unit in one enrolment: pen and
|
|
13
|
+
* highlighter strokes, their points as fractions of the page. One per enrolment, unit and page. The learner's own -
|
|
14
|
+
* read and written by them alone, never by a manager or a department head, and no part of a completion or its evidence.
|
|
15
|
+
* An enrolment takes one version, so a note stays on the page it was written on; a new version is a new enrolment.
|
|
16
|
+
*/
|
|
17
|
+
let LessonNote = class LessonNote {
|
|
18
|
+
};
|
|
19
|
+
exports.LessonNote = LessonNote;
|
|
20
|
+
tslib_1.__decorate([
|
|
21
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
22
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Note id.' }),
|
|
23
|
+
tslib_1.__metadata("design:type", String)
|
|
24
|
+
], LessonNote.prototype, "id", void 0);
|
|
25
|
+
tslib_1.__decorate([
|
|
26
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
27
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'The domain.' }),
|
|
28
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
29
|
+
], LessonNote.prototype, "domain", void 0);
|
|
30
|
+
tslib_1.__decorate([
|
|
31
|
+
(0, typeorm_1.RelationId)((row) => row.domain),
|
|
32
|
+
tslib_1.__metadata("design:type", String)
|
|
33
|
+
], LessonNote.prototype, "domainId", void 0);
|
|
34
|
+
tslib_1.__decorate([
|
|
35
|
+
(0, typeorm_1.ManyToOne)(type => enrollment_1.Enrollment),
|
|
36
|
+
(0, type_graphql_1.Field)(type => enrollment_1.Enrollment, { nullable: true, description: 'The enrolment.' }),
|
|
37
|
+
tslib_1.__metadata("design:type", enrollment_1.Enrollment)
|
|
38
|
+
], LessonNote.prototype, "enrollment", void 0);
|
|
39
|
+
tslib_1.__decorate([
|
|
40
|
+
(0, typeorm_1.RelationId)((row) => row.enrollment),
|
|
41
|
+
tslib_1.__metadata("design:type", String)
|
|
42
|
+
], LessonNote.prototype, "enrollmentId", void 0);
|
|
43
|
+
tslib_1.__decorate([
|
|
44
|
+
(0, typeorm_1.ManyToOne)(type => course_unit_1.CourseUnit),
|
|
45
|
+
(0, type_graphql_1.Field)(type => course_unit_1.CourseUnit, { nullable: true, description: 'The unit.' }),
|
|
46
|
+
tslib_1.__metadata("design:type", course_unit_1.CourseUnit)
|
|
47
|
+
], LessonNote.prototype, "unit", void 0);
|
|
48
|
+
tslib_1.__decorate([
|
|
49
|
+
(0, typeorm_1.RelationId)((row) => row.unit),
|
|
50
|
+
tslib_1.__metadata("design:type", String)
|
|
51
|
+
], LessonNote.prototype, "unitId", void 0);
|
|
52
|
+
tslib_1.__decorate([
|
|
53
|
+
(0, typeorm_1.Column)({ type: 'int' }),
|
|
54
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'The page of the PDF.' }),
|
|
55
|
+
tslib_1.__metadata("design:type", Number)
|
|
56
|
+
], LessonNote.prototype, "page", void 0);
|
|
57
|
+
tslib_1.__decorate([
|
|
58
|
+
(0, typeorm_1.Column)({ type: 'simple-json' }),
|
|
59
|
+
(0, type_graphql_1.Field)(type => shell_1.ScalarObject, {
|
|
60
|
+
description: 'The strokes - each { tool: pen | highlighter, color, width, points: [x, y, ...] }, as fractions of the page.'
|
|
61
|
+
}),
|
|
62
|
+
tslib_1.__metadata("design:type", Array)
|
|
63
|
+
], LessonNote.prototype, "strokes", void 0);
|
|
64
|
+
tslib_1.__decorate([
|
|
65
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
66
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Creation timestamp.' }),
|
|
67
|
+
tslib_1.__metadata("design:type", Date)
|
|
68
|
+
], LessonNote.prototype, "createdAt", void 0);
|
|
69
|
+
tslib_1.__decorate([
|
|
70
|
+
(0, typeorm_1.UpdateDateColumn)(),
|
|
71
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Last update timestamp.' }),
|
|
72
|
+
tslib_1.__metadata("design:type", Date)
|
|
73
|
+
], LessonNote.prototype, "updatedAt", void 0);
|
|
74
|
+
tslib_1.__decorate([
|
|
75
|
+
(0, typeorm_1.DeleteDateColumn)(),
|
|
76
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Soft delete timestamp.' }),
|
|
77
|
+
tslib_1.__metadata("design:type", Date)
|
|
78
|
+
], LessonNote.prototype, "deletedAt", void 0);
|
|
79
|
+
tslib_1.__decorate([
|
|
80
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
81
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who made it.' }),
|
|
82
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
83
|
+
], LessonNote.prototype, "creator", void 0);
|
|
84
|
+
tslib_1.__decorate([
|
|
85
|
+
(0, typeorm_1.RelationId)((row) => row.creator),
|
|
86
|
+
tslib_1.__metadata("design:type", String)
|
|
87
|
+
], LessonNote.prototype, "creatorId", void 0);
|
|
88
|
+
tslib_1.__decorate([
|
|
89
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
90
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who changed it last.' }),
|
|
91
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
92
|
+
], LessonNote.prototype, "updater", void 0);
|
|
93
|
+
tslib_1.__decorate([
|
|
94
|
+
(0, typeorm_1.RelationId)((row) => row.updater),
|
|
95
|
+
tslib_1.__metadata("design:type", String)
|
|
96
|
+
], LessonNote.prototype, "updaterId", void 0);
|
|
97
|
+
exports.LessonNote = LessonNote = tslib_1.__decorate([
|
|
98
|
+
(0, typeorm_1.Entity)({ name: 'learning_lesson_notes' }),
|
|
99
|
+
(0, typeorm_1.Index)('ix_learning_lesson_note_0', (row) => [row.domain, row.enrollment, row.unit, row.page], {
|
|
100
|
+
unique: true,
|
|
101
|
+
where: '"deleted_at" IS NULL'
|
|
102
|
+
}),
|
|
103
|
+
(0, type_graphql_1.ObjectType)({
|
|
104
|
+
description: "A learner's notes over one page of a PDF unit - pen and highlighter strokes, theirs alone."
|
|
105
|
+
})
|
|
106
|
+
], LessonNote);
|
|
107
|
+
//# sourceMappingURL=lesson-note.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lesson-note.js","sourceRoot":"","sources":["../../../server/service/enrollment/lesson-note.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, ID, Int, ObjectType } from 'type-graphql'\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\n\nimport { User } from '@things-factory/auth-base'\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\nimport { CourseUnit } from '../course/course-unit'\nimport { Enrollment } from './enrollment'\n\n/**\n * 노트 (ADR-0194 amendment 2-3~7) - what a learner wrote over one page of a PDF unit in one enrolment: pen and\n * highlighter strokes, their points as fractions of the page. One per enrolment, unit and page. The learner's own -\n * read and written by them alone, never by a manager or a department head, and no part of a completion or its evidence.\n * An enrolment takes one version, so a note stays on the page it was written on; a new version is a new enrolment.\n */\n@Entity({ name: 'learning_lesson_notes' })\n@Index('ix_learning_lesson_note_0', (row: LessonNote) => [row.domain, row.enrollment, row.unit, row.page], {\n unique: true,\n where: '\"deleted_at\" IS NULL'\n})\n@ObjectType({\n description: \"A learner's notes over one page of a PDF unit - pen and highlighter strokes, theirs alone.\"\n})\nexport class LessonNote {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Note id.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain.' })\n domain?: Domain\n\n @RelationId((row: LessonNote) => row.domain)\n domainId?: string\n\n @ManyToOne(type => Enrollment)\n @Field(type => Enrollment, { nullable: true, description: 'The enrolment.' })\n enrollment?: Enrollment\n\n @RelationId((row: LessonNote) => row.enrollment)\n enrollmentId?: string\n\n @ManyToOne(type => CourseUnit)\n @Field(type => CourseUnit, { nullable: true, description: 'The unit.' })\n unit?: CourseUnit\n\n @RelationId((row: LessonNote) => row.unit)\n unitId?: string\n\n @Column({ type: 'int' })\n @Field(type => Int, { description: 'The page of the PDF.' })\n page: number\n\n @Column({ type: 'simple-json' })\n @Field(type => ScalarObject, {\n description:\n 'The strokes - each { tool: pen | highlighter, color, width, points: [x, y, ...] }, as fractions of the page.'\n })\n strokes: LessonNoteStroke[]\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Creation timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp.' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'Soft delete timestamp.' })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who made it.' })\n creator?: User\n\n @RelationId((row: LessonNote) => row.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who changed it last.' })\n updater?: User\n\n @RelationId((row: LessonNote) => row.updater)\n updaterId?: string\n}\n\n/** One stroke of a note - `@operato/learning`'s InkStroke. */\nexport interface LessonNoteStroke {\n tool: 'pen' | 'highlighter'\n color: string\n width: number\n points: number[]\n}\n"]}
|
|
@@ -70,6 +70,10 @@ export declare function unitDone(unit: UnitShape, progress: {
|
|
|
70
70
|
watchedSeconds: number;
|
|
71
71
|
pagesRead?: number | null;
|
|
72
72
|
}): boolean;
|
|
73
|
+
/** The seconds a unit takes to be done, by `unitDone`'s own rule: a video its length, a PDF or text its least time, a link none. */
|
|
74
|
+
export declare const unitSeconds: (unit: UnitShape) => number;
|
|
75
|
+
/** The seconds a version takes at least - every unit done, and the version's least time: the longer of the two. */
|
|
76
|
+
export declare const expectedSecondsOf: (units: UnitShape[], minSeconds: number | null | undefined) => number;
|
|
73
77
|
export interface CompletionFacts {
|
|
74
78
|
/** The units that count, and the ones of them done. */
|
|
75
79
|
units: number;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* time a tick adds, where a video may stand, when a unit is done, whether an enrolment is complete, and a quiz's score.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.overdue = exports.counts = exports.DEFAULT_UNIT_SECONDS = exports.POSITION_SLACK_SECONDS = exports.TICK_SECONDS = void 0;
|
|
7
|
+
exports.overdue = exports.expectedSecondsOf = exports.unitSeconds = exports.counts = exports.DEFAULT_UNIT_SECONDS = exports.POSITION_SLACK_SECONDS = exports.TICK_SECONDS = void 0;
|
|
8
8
|
exports.secondsPerPage = secondsPerPage;
|
|
9
9
|
exports.tickVerdict = tickVerdict;
|
|
10
10
|
exports.unitDone = unitDone;
|
|
@@ -83,6 +83,12 @@ function unitDone(unit, progress) {
|
|
|
83
83
|
return progress.learnedSeconds >= (unit.minSeconds ?? exports.DEFAULT_UNIT_SECONDS);
|
|
84
84
|
return false;
|
|
85
85
|
}
|
|
86
|
+
/** The seconds a unit takes to be done, by `unitDone`'s own rule: a video its length, a PDF or text its least time, a link none. */
|
|
87
|
+
const unitSeconds = (unit) => unit.kind === 'VIDEO' ? (unit.durationSeconds ?? 0) : (0, exports.counts)(unit) ? (unit.minSeconds ?? exports.DEFAULT_UNIT_SECONDS) : 0;
|
|
88
|
+
exports.unitSeconds = unitSeconds;
|
|
89
|
+
/** The seconds a version takes at least - every unit done, and the version's least time: the longer of the two. */
|
|
90
|
+
const expectedSecondsOf = (units, minSeconds) => Math.max(minSeconds ?? 0, units.reduce((sum, unit) => sum + (0, exports.unitSeconds)(unit), 0));
|
|
91
|
+
exports.expectedSecondsOf = expectedSecondsOf;
|
|
86
92
|
/**
|
|
87
93
|
* What still stands between an enrolment and its completion (decision 8) - every unit done, the least time, the quiz
|
|
88
94
|
* passed when there is one, the acknowledgement confirmed when one is asked. Complete when nothing is missing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress-rules.js","sourceRoot":"","sources":["../../../server/service/enrollment/progress-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What the server decides of learning by itself (ADR-0194 decisions 4, 6, 8) - data in, verdict out, no database: the\n * time a tick adds, where a video may stand, when a unit is done, whether an enrolment is complete, and a quiz's score.\n */\n\n/** The longest a tick is counted for, in seconds - the screen sends one every 15 seconds looked at. */\nexport const TICK_SECONDS = 15\n/** How far ahead of what was watched a position still reads as watching - the video's own step between two updates. */\nexport const POSITION_SLACK_SECONDS = 2\n/** How long a PDF or a text is looked at to be done when its unit says nothing. */\nexport const DEFAULT_UNIT_SECONDS = 60\n\nexport interface ProgressNow {\n learnedSeconds: number\n watchedSeconds: number\n /** The furthest page of a PDF arrived at. */\n pagesSeen?: number | null\n /** The furthest page of a PDF read through - looked at for its seconds. */\n pagesRead?: number | null\n /** The seconds looked at on the furthest page arrived at, so far. */\n pageSeconds?: number | null\n /** The page it was on until this tick. */\n pageAt?: number | null\n lastTickAt?: Date | null\n}\n\nexport interface Tick {\n /** What the screen says it looked at since its last tick - it may lower what is counted, never raise it. */\n seconds: number\n /** Where it stands - a video's second, a PDF's page (from 1); 0 for a text. */\n position: number\n}\n\nexport interface UnitShape {\n kind: string\n durationSeconds?: number | null\n pageCount?: number | null\n minSeconds?: number | null\n}\n\nexport type TickVerdict =\n | { refusal: 'position-ahead'; allowed: number }\n | { refusal: 'page-ahead'; allowed: number }\n | { add: number; watchedSeconds: number; positionSeconds: number; pagesSeen: number; pagesRead: number; pageSeconds: number; pageAt: number }\n\n/** The fewest seconds a PDF page is looked at to count as read - its unit's time shared by its pages, 3 at least. */\nexport function secondsPerPage(unit: UnitShape): number {\n return Math.max(3, Math.ceil((unit.minSeconds ?? DEFAULT_UNIT_SECONDS) / Math.max(1, unit.pageCount ?? 1)))\n}\n\n/**\n * What one tick does (decision 6). The time added is the gap since the last tick, at most 15 seconds and at most what\n * the screen says - a tick with no tick before it starts the clock and adds nothing.\n *\n * - A video's position may not be ahead of what was watched by more than the time that passed: a move ahead is refused,\n * a move back is free.\n * - A PDF is read page by page (2026-11-08, the user). The seconds since the last tick were spent on the page it was on;\n * the furthest page arrived at is read once its own seconds reach `secondsPerPage`. A tick may turn only to a page\n * read or the one after the furthest read - two turns at the same moment do not read the page between them; going\n * back is free.\n */\nexport function tickVerdict(progress: ProgressNow, tick: Tick, now: Date, unit: UnitShape): TickVerdict {\n const gap = progress.lastTickAt ? Math.max(0, (now.getTime() - new Date(progress.lastTickAt).getTime()) / 1000) : 0\n const said = Math.max(0, Number(tick.seconds) || 0)\n const add = progress.lastTickAt ? Math.floor(Math.min(TICK_SECONDS, gap, said)) : 0\n const position = Math.max(0, Math.floor(Number(tick.position) || 0))\n let seen = progress.pagesSeen ?? 0\n let read = progress.pagesRead ?? 0\n let onPage = progress.pageSeconds ?? 0\n const kept = { pagesSeen: seen, pagesRead: read, pageSeconds: onPage }\n if (unit.kind === 'VIDEO') {\n const allowed = progress.watchedSeconds + Math.min(TICK_SECONDS, gap) + POSITION_SLACK_SECONDS\n if (position > allowed) return { refusal: 'position-ahead', allowed: Math.floor(progress.watchedSeconds) }\n return { add, watchedSeconds: Math.max(progress.watchedSeconds, position), positionSeconds: position, ...kept, pageAt: 0 }\n }\n if (unit.kind === 'PDF') {\n /* The time since the last tick goes to the page it was on - it reads the furthest page only when it was there. */\n const was = progress.pageAt ?? 0\n if (was > 0 && was === seen) {\n onPage += add\n if (onPage >= secondsPerPage(unit)) read = Math.max(read, seen)\n }\n const page = Math.max(1, position)\n if (page > read + 1) return { refusal: 'page-ahead', allowed: read }\n if (page > seen) {\n seen = page\n onPage = 0\n }\n return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0, pagesSeen: seen, pagesRead: read, pageSeconds: onPage, pageAt: page }\n }\n return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0, ...kept, pageAt: 0 }\n}\n\n/** Whether a unit counts toward completion - a link does not: the server cannot know what was watched elsewhere. */\nexport const counts = (unit: UnitShape) => unit.kind !== 'LINK'\n\n/**\n * Whether a unit is done (decision 3): a video watched to its end (its length known) **and** its running time counted\n * by the server - a skip or a faster playback reaches neither; a PDF with every page read through - the last one too (its\n * page count known) - and its time; a text looked at for its time. A link is never done - it does not count.\n */\nexport function unitDone(unit: UnitShape, progress: { learnedSeconds: number; watchedSeconds: number; pagesRead?: number | null }): boolean {\n if (unit.kind === 'VIDEO')\n return !!unit.durationSeconds && progress.watchedSeconds >= unit.durationSeconds - POSITION_SLACK_SECONDS && progress.learnedSeconds >= unit.durationSeconds - TICK_SECONDS\n if (unit.kind === 'PDF') return !!unit.pageCount && (progress.pagesRead ?? 0) >= unit.pageCount && progress.learnedSeconds >= (unit.minSeconds ?? DEFAULT_UNIT_SECONDS)\n if (unit.kind === 'MARKDOWN') return progress.learnedSeconds >= (unit.minSeconds ?? DEFAULT_UNIT_SECONDS)\n return false\n}\n\nexport interface CompletionFacts {\n /** The units that count, and the ones of them done. */\n units: number\n unitsDone: number\n learnedSeconds: number\n minSeconds: number\n /** The version has a quiz - questions and a pass score. */\n quiz: boolean\n passed: boolean\n /** The version asks for an acknowledgement. */\n acknowledgement: boolean\n acknowledged: boolean\n}\n\nexport type Missing = 'units' | 'time' | 'quiz' | 'acknowledgement'\n\n/**\n * What still stands between an enrolment and its completion (decision 8) - every unit done, the least time, the quiz\n * passed when there is one, the acknowledgement confirmed when one is asked. Complete when nothing is missing.\n */\nexport function missingOf(facts: CompletionFacts): Missing[] {\n const missing: Missing[] = []\n if (facts.unitsDone < facts.units) missing.push('units')\n if (facts.learnedSeconds < facts.minSeconds) missing.push('time')\n if (facts.quiz && !facts.passed) missing.push('quiz')\n if (facts.acknowledgement && !facts.acknowledged) missing.push('acknowledgement')\n return missing\n}\n\nexport interface QuestionKey {\n id: string\n kind: string\n choices: { id: string; correct: boolean }[]\n}\n\n/**\n * A quiz's score, out of 100 (decision 4): each question is right when the choices picked are exactly the right ones -\n * one for SINGLE and TRUE_FALSE, every right one and no other for MULTIPLE.\n */\nexport function scoreOf(questions: QuestionKey[], answers: Record<string, string[]>): number {\n if (!questions.length) return 0\n const right = questions.filter(question => {\n const picked = new Set(answers?.[question.id] ?? [])\n const correct = question.choices.filter(one => one.correct).map(one => one.id)\n return picked.size === correct.length && correct.every(id => picked.has(id))\n }).length\n return Math.round((right / questions.length) * 100)\n}\n\n/** The questions an attempt draws - `count` of them (all when empty), in a shuffled order, each with its choices shuffled. */\nexport function drawQuestions<Q extends { id: string; choices: { id: string }[] }>(questions: Q[], count: number | null | undefined, random: () => number = Math.random) {\n const shuffle = <T>(items: T[]): T[] => {\n const out = [...items]\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(random() * (i + 1))\n ;[out[i], out[j]] = [out[j], out[i]]\n }\n return out\n }\n const picked = shuffle(questions).slice(0, count && count > 0 ? count : questions.length)\n return picked.map(question => ({ questionId: question.id, choiceIds: shuffle(question.choices.map(one => one.id)) }))\n}\n\n/** The day an enrolment reads as overdue: open, and its due day before today. */\nexport const overdue = (enrollment: { status: string; dueOn?: string | null }, today: string) =>\n (enrollment.status === 'ASSIGNED' || enrollment.status === 'IN_PROGRESS') && !!enrollment.dueOn && enrollment.dueOn < today\n"]}
|
|
1
|
+
{"version":3,"file":"progress-rules.js","sourceRoot":"","sources":["../../../server/service/enrollment/progress-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What the server decides of learning by itself (ADR-0194 decisions 4, 6, 8) - data in, verdict out, no database: the\n * time a tick adds, where a video may stand, when a unit is done, whether an enrolment is complete, and a quiz's score.\n */\n\n/** The longest a tick is counted for, in seconds - the screen sends one every 15 seconds looked at. */\nexport const TICK_SECONDS = 15\n/** How far ahead of what was watched a position still reads as watching - the video's own step between two updates. */\nexport const POSITION_SLACK_SECONDS = 2\n/** How long a PDF or a text is looked at to be done when its unit says nothing. */\nexport const DEFAULT_UNIT_SECONDS = 60\n\nexport interface ProgressNow {\n learnedSeconds: number\n watchedSeconds: number\n /** The furthest page of a PDF arrived at. */\n pagesSeen?: number | null\n /** The furthest page of a PDF read through - looked at for its seconds. */\n pagesRead?: number | null\n /** The seconds looked at on the furthest page arrived at, so far. */\n pageSeconds?: number | null\n /** The page it was on until this tick. */\n pageAt?: number | null\n lastTickAt?: Date | null\n}\n\nexport interface Tick {\n /** What the screen says it looked at since its last tick - it may lower what is counted, never raise it. */\n seconds: number\n /** Where it stands - a video's second, a PDF's page (from 1); 0 for a text. */\n position: number\n}\n\nexport interface UnitShape {\n kind: string\n durationSeconds?: number | null\n pageCount?: number | null\n minSeconds?: number | null\n}\n\nexport type TickVerdict =\n | { refusal: 'position-ahead'; allowed: number }\n | { refusal: 'page-ahead'; allowed: number }\n | { add: number; watchedSeconds: number; positionSeconds: number; pagesSeen: number; pagesRead: number; pageSeconds: number; pageAt: number }\n\n/** The fewest seconds a PDF page is looked at to count as read - its unit's time shared by its pages, 3 at least. */\nexport function secondsPerPage(unit: UnitShape): number {\n return Math.max(3, Math.ceil((unit.minSeconds ?? DEFAULT_UNIT_SECONDS) / Math.max(1, unit.pageCount ?? 1)))\n}\n\n/**\n * What one tick does (decision 6). The time added is the gap since the last tick, at most 15 seconds and at most what\n * the screen says - a tick with no tick before it starts the clock and adds nothing.\n *\n * - A video's position may not be ahead of what was watched by more than the time that passed: a move ahead is refused,\n * a move back is free.\n * - A PDF is read page by page (2026-11-08, the user). The seconds since the last tick were spent on the page it was on;\n * the furthest page arrived at is read once its own seconds reach `secondsPerPage`. A tick may turn only to a page\n * read or the one after the furthest read - two turns at the same moment do not read the page between them; going\n * back is free.\n */\nexport function tickVerdict(progress: ProgressNow, tick: Tick, now: Date, unit: UnitShape): TickVerdict {\n const gap = progress.lastTickAt ? Math.max(0, (now.getTime() - new Date(progress.lastTickAt).getTime()) / 1000) : 0\n const said = Math.max(0, Number(tick.seconds) || 0)\n const add = progress.lastTickAt ? Math.floor(Math.min(TICK_SECONDS, gap, said)) : 0\n const position = Math.max(0, Math.floor(Number(tick.position) || 0))\n let seen = progress.pagesSeen ?? 0\n let read = progress.pagesRead ?? 0\n let onPage = progress.pageSeconds ?? 0\n const kept = { pagesSeen: seen, pagesRead: read, pageSeconds: onPage }\n if (unit.kind === 'VIDEO') {\n const allowed = progress.watchedSeconds + Math.min(TICK_SECONDS, gap) + POSITION_SLACK_SECONDS\n if (position > allowed) return { refusal: 'position-ahead', allowed: Math.floor(progress.watchedSeconds) }\n return { add, watchedSeconds: Math.max(progress.watchedSeconds, position), positionSeconds: position, ...kept, pageAt: 0 }\n }\n if (unit.kind === 'PDF') {\n /* The time since the last tick goes to the page it was on - it reads the furthest page only when it was there. */\n const was = progress.pageAt ?? 0\n if (was > 0 && was === seen) {\n onPage += add\n if (onPage >= secondsPerPage(unit)) read = Math.max(read, seen)\n }\n const page = Math.max(1, position)\n if (page > read + 1) return { refusal: 'page-ahead', allowed: read }\n if (page > seen) {\n seen = page\n onPage = 0\n }\n return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0, pagesSeen: seen, pagesRead: read, pageSeconds: onPage, pageAt: page }\n }\n return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0, ...kept, pageAt: 0 }\n}\n\n/** Whether a unit counts toward completion - a link does not: the server cannot know what was watched elsewhere. */\nexport const counts = (unit: UnitShape) => unit.kind !== 'LINK'\n\n/**\n * Whether a unit is done (decision 3): a video watched to its end (its length known) **and** its running time counted\n * by the server - a skip or a faster playback reaches neither; a PDF with every page read through - the last one too (its\n * page count known) - and its time; a text looked at for its time. A link is never done - it does not count.\n */\nexport function unitDone(unit: UnitShape, progress: { learnedSeconds: number; watchedSeconds: number; pagesRead?: number | null }): boolean {\n if (unit.kind === 'VIDEO')\n return !!unit.durationSeconds && progress.watchedSeconds >= unit.durationSeconds - POSITION_SLACK_SECONDS && progress.learnedSeconds >= unit.durationSeconds - TICK_SECONDS\n if (unit.kind === 'PDF') return !!unit.pageCount && (progress.pagesRead ?? 0) >= unit.pageCount && progress.learnedSeconds >= (unit.minSeconds ?? DEFAULT_UNIT_SECONDS)\n if (unit.kind === 'MARKDOWN') return progress.learnedSeconds >= (unit.minSeconds ?? DEFAULT_UNIT_SECONDS)\n return false\n}\n\n/** The seconds a unit takes to be done, by `unitDone`'s own rule: a video its length, a PDF or text its least time, a link none. */\nexport const unitSeconds = (unit: UnitShape): number =>\n unit.kind === 'VIDEO' ? (unit.durationSeconds ?? 0) : counts(unit) ? (unit.minSeconds ?? DEFAULT_UNIT_SECONDS) : 0\n\n/** The seconds a version takes at least - every unit done, and the version's least time: the longer of the two. */\nexport const expectedSecondsOf = (units: UnitShape[], minSeconds: number | null | undefined): number =>\n Math.max(minSeconds ?? 0, units.reduce((sum, unit) => sum + unitSeconds(unit), 0))\n\nexport interface CompletionFacts {\n /** The units that count, and the ones of them done. */\n units: number\n unitsDone: number\n learnedSeconds: number\n minSeconds: number\n /** The version has a quiz - questions and a pass score. */\n quiz: boolean\n passed: boolean\n /** The version asks for an acknowledgement. */\n acknowledgement: boolean\n acknowledged: boolean\n}\n\nexport type Missing = 'units' | 'time' | 'quiz' | 'acknowledgement'\n\n/**\n * What still stands between an enrolment and its completion (decision 8) - every unit done, the least time, the quiz\n * passed when there is one, the acknowledgement confirmed when one is asked. Complete when nothing is missing.\n */\nexport function missingOf(facts: CompletionFacts): Missing[] {\n const missing: Missing[] = []\n if (facts.unitsDone < facts.units) missing.push('units')\n if (facts.learnedSeconds < facts.minSeconds) missing.push('time')\n if (facts.quiz && !facts.passed) missing.push('quiz')\n if (facts.acknowledgement && !facts.acknowledged) missing.push('acknowledgement')\n return missing\n}\n\nexport interface QuestionKey {\n id: string\n kind: string\n choices: { id: string; correct: boolean }[]\n}\n\n/**\n * A quiz's score, out of 100 (decision 4): each question is right when the choices picked are exactly the right ones -\n * one for SINGLE and TRUE_FALSE, every right one and no other for MULTIPLE.\n */\nexport function scoreOf(questions: QuestionKey[], answers: Record<string, string[]>): number {\n if (!questions.length) return 0\n const right = questions.filter(question => {\n const picked = new Set(answers?.[question.id] ?? [])\n const correct = question.choices.filter(one => one.correct).map(one => one.id)\n return picked.size === correct.length && correct.every(id => picked.has(id))\n }).length\n return Math.round((right / questions.length) * 100)\n}\n\n/** The questions an attempt draws - `count` of them (all when empty), in a shuffled order, each with its choices shuffled. */\nexport function drawQuestions<Q extends { id: string; choices: { id: string }[] }>(questions: Q[], count: number | null | undefined, random: () => number = Math.random) {\n const shuffle = <T>(items: T[]): T[] => {\n const out = [...items]\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(random() * (i + 1))\n ;[out[i], out[j]] = [out[j], out[i]]\n }\n return out\n }\n const picked = shuffle(questions).slice(0, count && count > 0 ? count : questions.length)\n return picked.map(question => ({ questionId: question.id, choiceIds: shuffle(question.choices.map(one => one.id)) }))\n}\n\n/** The day an enrolment reads as overdue: open, and its due day before today. */\nexport const overdue = (enrollment: { status: string; dueOn?: string | null }, today: string) =>\n (enrollment.status === 'ASSIGNED' || enrollment.status === 'IN_PROGRESS') && !!enrollment.dueOn && enrollment.dueOn < today\n"]}
|
|
@@ -6,9 +6,10 @@ export { COURSE_UNIT_REF_TYPE, publishedVersionOf, unitsOf, questionsOf, addDraf
|
|
|
6
6
|
export * from './enrollment/enrollment';
|
|
7
7
|
export * from './enrollment/quiz-attempt';
|
|
8
8
|
export * from './enrollment/unit-progress';
|
|
9
|
+
export * from './enrollment/lesson-note';
|
|
9
10
|
export * from './enrollment/learning-outcome';
|
|
10
|
-
export {
|
|
11
|
-
export declare const entities: (typeof import("./course").Course | typeof import("./course").CourseVersion | typeof import("./course").CourseUnit | typeof import("./course").CourseQuestion | typeof import("./enrollment").Enrollment | typeof import("./enrollment").QuizAttempt | typeof import("./enrollment").UnitProgress)[];
|
|
11
|
+
export { cancelEnrollments, DIRECT_SOURCE_KIND, enroll, learningAddressOf, withdraw, type LearningSource, LEARNING_CERTIFICATE_KIND, LEARNING_REGISTER_KIND } from './enrollment';
|
|
12
|
+
export declare const entities: (typeof import("./course").Course | typeof import("./course").CourseVersion | typeof import("./course").CourseUnit | typeof import("./course").CourseQuestion | typeof import("./enrollment").Enrollment | typeof import("./enrollment").QuizAttempt | typeof import("./enrollment").UnitProgress | typeof import("./enrollment").LessonNote)[];
|
|
12
13
|
export declare const schema: {
|
|
13
|
-
resolverClasses: (typeof import("./course/material-draft-mutation").MaterialDraftMutation | typeof import("./course/course-mutation").CourseMutation | typeof import("./course/course-query").CourseQuery | typeof import("./course/course-query").CourseVersionFields | typeof import("./course/course-query").CourseUnitFields | typeof import("./course/course-query").CourseQuestionFields | typeof import("./enrollment/enrollment-mutation").EnrollmentMutation | typeof import("./enrollment/enrollment-query").EnrollmentQuery)[];
|
|
14
|
+
resolverClasses: (typeof import("./course/material-draft-mutation").MaterialDraftMutation | typeof import("./course/course-mutation").CourseMutation | typeof import("./course/course-query").CourseQuery | typeof import("./course/course-query").CourseVersionFields | typeof import("./course/course-query").CourseUnitFields | typeof import("./course/course-query").CourseQuestionFields | typeof import("./enrollment/enrollment-mutation").EnrollmentMutation | typeof import("./enrollment/enrollment-query").EnrollmentQuery | typeof import("./enrollment/enrollment-query").CourseEnrollmentFields)[];
|
|
14
15
|
};
|