@things-factory/learning 0.0.0-stage → 10.1.261

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/dist-client/bootstrap.d.ts +2 -0
  2. package/dist-client/bootstrap.js +4 -0
  3. package/dist-client/bootstrap.js.map +1 -0
  4. package/dist-client/components/learning-assign-form.d.ts +24 -0
  5. package/dist-client/components/learning-assign-form.js +77 -0
  6. package/dist-client/components/learning-assign-form.js.map +1 -0
  7. package/dist-client/components/learning-course-form.d.ts +29 -0
  8. package/dist-client/components/learning-course-form.js +104 -0
  9. package/dist-client/components/learning-course-form.js.map +1 -0
  10. package/dist-client/components/learning-material-form.d.ts +41 -0
  11. package/dist-client/components/learning-material-form.js +202 -0
  12. package/dist-client/components/learning-material-form.js.map +1 -0
  13. package/dist-client/components/learning-question-form.d.ts +33 -0
  14. package/dist-client/components/learning-question-form.js +142 -0
  15. package/dist-client/components/learning-question-form.js.map +1 -0
  16. package/dist-client/components/learning-todo.d.ts +12 -0
  17. package/dist-client/components/learning-todo.js +43 -0
  18. package/dist-client/components/learning-todo.js.map +1 -0
  19. package/dist-client/components/learning-unit-form.d.ts +44 -0
  20. package/dist-client/components/learning-unit-form.js +151 -0
  21. package/dist-client/components/learning-unit-form.js.map +1 -0
  22. package/dist-client/index.d.ts +2 -0
  23. package/dist-client/index.js +3 -0
  24. package/dist-client/index.js.map +1 -0
  25. package/dist-client/pages/learning-course-editor.d.ts +63 -0
  26. package/dist-client/pages/learning-course-editor.js +476 -0
  27. package/dist-client/pages/learning-course-editor.js.map +1 -0
  28. package/dist-client/pages/learning-course-list.d.ts +37 -0
  29. package/dist-client/pages/learning-course-list.js +92 -0
  30. package/dist-client/pages/learning-course-list.js.map +1 -0
  31. package/dist-client/pages/learning-enrollment-status.d.ts +65 -0
  32. package/dist-client/pages/learning-enrollment-status.js +211 -0
  33. package/dist-client/pages/learning-enrollment-status.js.map +1 -0
  34. package/dist-client/pages/learning-view.d.ts +63 -0
  35. package/dist-client/pages/learning-view.js +446 -0
  36. package/dist-client/pages/learning-view.js.map +1 -0
  37. package/dist-client/pages/my-learning.d.ts +22 -0
  38. package/dist-client/pages/my-learning.js +110 -0
  39. package/dist-client/pages/my-learning.js.map +1 -0
  40. package/dist-client/route.d.ts +1 -0
  41. package/dist-client/route.js +21 -0
  42. package/dist-client/route.js.map +1 -0
  43. package/dist-client/tsconfig.tsbuildinfo +1 -0
  44. package/dist-client/utils/learning-api.d.ts +61 -0
  45. package/dist-client/utils/learning-api.js +85 -0
  46. package/dist-client/utils/learning-api.js.map +1 -0
  47. package/dist-client/utils/learning-words.d.ts +20 -0
  48. package/dist-client/utils/learning-words.js +22 -0
  49. package/dist-client/utils/learning-words.js.map +1 -0
  50. package/dist-client/utils/material-source.d.ts +12 -0
  51. package/dist-client/utils/material-source.js +77 -0
  52. package/dist-client/utils/material-source.js.map +1 -0
  53. package/dist-server/activities.d.ts +38 -0
  54. package/dist-server/activities.js +42 -0
  55. package/dist-server/activities.js.map +1 -0
  56. package/dist-server/ai-reads.d.ts +9 -0
  57. package/dist-server/ai-reads.js +70 -0
  58. package/dist-server/ai-reads.js.map +1 -0
  59. package/dist-server/attachment-reader.d.ts +1 -0
  60. package/dist-server/attachment-reader.js +23 -0
  61. package/dist-server/attachment-reader.js.map +1 -0
  62. package/dist-server/index.d.ts +4 -0
  63. package/dist-server/index.js +33 -0
  64. package/dist-server/index.js.map +1 -0
  65. package/dist-server/learning-subjects.d.ts +10 -0
  66. package/dist-server/learning-subjects.js +31 -0
  67. package/dist-server/learning-subjects.js.map +1 -0
  68. package/dist-server/role-tasks.d.ts +2 -0
  69. package/dist-server/role-tasks.js +17 -0
  70. package/dist-server/role-tasks.js.map +1 -0
  71. package/dist-server/service/course/course-mutation.d.ts +19 -0
  72. package/dist-server/service/course/course-mutation.js +177 -0
  73. package/dist-server/service/course/course-mutation.js.map +1 -0
  74. package/dist-server/service/course/course-query.d.ts +27 -0
  75. package/dist-server/service/course/course-query.js +183 -0
  76. package/dist-server/service/course/course-query.js.map +1 -0
  77. package/dist-server/service/course/course-question.d.ts +41 -0
  78. package/dist-server/service/course/course-question.js +116 -0
  79. package/dist-server/service/course/course-question.js.map +1 -0
  80. package/dist-server/service/course/course-rules.d.ts +51 -0
  81. package/dist-server/service/course/course-rules.js +93 -0
  82. package/dist-server/service/course/course-rules.js.map +1 -0
  83. package/dist-server/service/course/course-service.d.ts +72 -0
  84. package/dist-server/service/course/course-service.js +398 -0
  85. package/dist-server/service/course/course-service.js.map +1 -0
  86. package/dist-server/service/course/course-type.d.ts +56 -0
  87. package/dist-server/service/course/course-type.js +202 -0
  88. package/dist-server/service/course/course-type.js.map +1 -0
  89. package/dist-server/service/course/course-unit.d.ts +37 -0
  90. package/dist-server/service/course/course-unit.js +128 -0
  91. package/dist-server/service/course/course-unit.js.map +1 -0
  92. package/dist-server/service/course/course-version.d.ts +45 -0
  93. package/dist-server/service/course/course-version.js +151 -0
  94. package/dist-server/service/course/course-version.js.map +1 -0
  95. package/dist-server/service/course/course.d.ts +23 -0
  96. package/dist-server/service/course/course.js +92 -0
  97. package/dist-server/service/course/course.js.map +1 -0
  98. package/dist-server/service/course/index.d.ts +15 -0
  99. package/dist-server/service/course/index.js +25 -0
  100. package/dist-server/service/course/index.js.map +1 -0
  101. package/dist-server/service/course/material-audio.d.ts +1 -0
  102. package/dist-server/service/course/material-audio.js +43 -0
  103. package/dist-server/service/course/material-audio.js.map +1 -0
  104. package/dist-server/service/course/material-draft-mutation.d.ts +13 -0
  105. package/dist-server/service/course/material-draft-mutation.js +55 -0
  106. package/dist-server/service/course/material-draft-mutation.js.map +1 -0
  107. package/dist-server/service/course/material-draft-rules.d.ts +38 -0
  108. package/dist-server/service/course/material-draft-rules.js +83 -0
  109. package/dist-server/service/course/material-draft-rules.js.map +1 -0
  110. package/dist-server/service/course/material-draft-service.d.ts +15 -0
  111. package/dist-server/service/course/material-draft-service.js +162 -0
  112. package/dist-server/service/course/material-draft-service.js.map +1 -0
  113. package/dist-server/service/enrollment/enrollment-mutation.d.ts +11 -0
  114. package/dist-server/service/enrollment/enrollment-mutation.js +124 -0
  115. package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -0
  116. package/dist-server/service/enrollment/enrollment-query.d.ts +24 -0
  117. package/dist-server/service/enrollment/enrollment-query.js +236 -0
  118. package/dist-server/service/enrollment/enrollment-query.js.map +1 -0
  119. package/dist-server/service/enrollment/enrollment-remind.d.ts +1 -0
  120. package/dist-server/service/enrollment/enrollment-remind.js +45 -0
  121. package/dist-server/service/enrollment/enrollment-remind.js.map +1 -0
  122. package/dist-server/service/enrollment/enrollment-service.d.ts +110 -0
  123. package/dist-server/service/enrollment/enrollment-service.js +427 -0
  124. package/dist-server/service/enrollment/enrollment-service.js.map +1 -0
  125. package/dist-server/service/enrollment/enrollment-status.d.ts +53 -0
  126. package/dist-server/service/enrollment/enrollment-status.js +194 -0
  127. package/dist-server/service/enrollment/enrollment-status.js.map +1 -0
  128. package/dist-server/service/enrollment/enrollment-type.d.ts +82 -0
  129. package/dist-server/service/enrollment/enrollment-type.js +293 -0
  130. package/dist-server/service/enrollment/enrollment-type.js.map +1 -0
  131. package/dist-server/service/enrollment/enrollment.d.ts +53 -0
  132. package/dist-server/service/enrollment/enrollment.js +191 -0
  133. package/dist-server/service/enrollment/enrollment.js.map +1 -0
  134. package/dist-server/service/enrollment/index.d.ts +15 -0
  135. package/dist-server/service/enrollment/index.js +26 -0
  136. package/dist-server/service/enrollment/index.js.map +1 -0
  137. package/dist-server/service/enrollment/learning-ai.d.ts +45 -0
  138. package/dist-server/service/enrollment/learning-ai.js +93 -0
  139. package/dist-server/service/enrollment/learning-ai.js.map +1 -0
  140. package/dist-server/service/enrollment/learning-documents.d.ts +7 -0
  141. package/dist-server/service/enrollment/learning-documents.js +159 -0
  142. package/dist-server/service/enrollment/learning-documents.js.map +1 -0
  143. package/dist-server/service/enrollment/learning-outcome.d.ts +10 -0
  144. package/dist-server/service/enrollment/learning-outcome.js +16 -0
  145. package/dist-server/service/enrollment/learning-outcome.js.map +1 -0
  146. package/dist-server/service/enrollment/progress-rules.d.ts +119 -0
  147. package/dist-server/service/enrollment/progress-rules.js +132 -0
  148. package/dist-server/service/enrollment/progress-rules.js.map +1 -0
  149. package/dist-server/service/enrollment/quiz-attempt.d.ts +34 -0
  150. package/dist-server/service/enrollment/quiz-attempt.js +105 -0
  151. package/dist-server/service/enrollment/quiz-attempt.js.map +1 -0
  152. package/dist-server/service/enrollment/unit-progress.d.ts +34 -0
  153. package/dist-server/service/enrollment/unit-progress.js +134 -0
  154. package/dist-server/service/enrollment/unit-progress.js.map +1 -0
  155. package/dist-server/service/index.d.ts +14 -0
  156. package/dist-server/service/index.js +43 -0
  157. package/dist-server/service/index.js.map +1 -0
  158. package/dist-server/tsconfig.tsbuildinfo +1 -0
  159. package/package.json +57 -4
  160. package/things-factory.config.js +14 -0
  161. package/translations/en.json +278 -0
  162. package/translations/es.json +278 -0
  163. package/translations/ja.json +278 -0
  164. package/translations/ko.json +278 -0
  165. package/translations/ms.json +278 -0
  166. package/translations/zh.json +278 -0
  167. package/README.md +0 -3
@@ -0,0 +1,61 @@
1
+ export declare const UNIT_FIELDS = "id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype }";
2
+ /** One unit's progress as the server keeps it - where it stands and the time it acknowledged. */
3
+ export declare const PROGRESS_FIELDS = "learnedSeconds watchedSeconds positionSeconds pagesSeen pagesRead pageSeconds pageAt doneAt";
4
+ export declare const LEARNING_FIELDS = "\n id status state dueOn startedAt learnedSeconds score attempts attemptsLeft quizSize missing acknowledgedAt completedAt certificateNo\n course { id name description category }\n version { id number minSeconds acknowledgement passScore questionCount attemptLimit }\n units { counts unit { id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype } } progress { learnedSeconds watchedSeconds positionSeconds pagesSeen pagesRead pageSeconds pageAt doneAt } }\n lastUnitId\n";
5
+ export declare const MY_LIST_FIELDS = "\n id status state dueOn completedAt certificateNo\n course { id name category }\n units { counts progress { doneAt } }\n";
6
+ export declare const VERSION_FIELDS = "\n id number status minSeconds acknowledgement passScore questionCount attemptLimit sourceKind sourceNote publishedAt closedAt\n units { id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype } }\n questions { id seq kind text explanation drafted choices { id text correct } }\n";
7
+ export declare const COURSE_FIELDS = "\n id name description category updatedAt\n manager { id name }\n publishedVersion { id number }\n versions { id number status publishedAt closedAt }\n";
8
+ export declare const STATUS_ROW_FIELDS = "\n id state departmentName units unitsDone versionNumber\n user { id name email }\n course { id name }\n enrollment { id status dueOn sourceKind startedAt completedAt learnedSeconds score attempts certificateNo cancelReason }\n";
9
+ /** Units done of those that count, from an enrolment's units. */
10
+ export declare function unitsDoneOf(units?: {
11
+ counts: boolean;
12
+ progress?: {
13
+ doneAt?: string | null;
14
+ } | null;
15
+ }[]): {
16
+ done: number;
17
+ total: number;
18
+ };
19
+ type UnitRow = {
20
+ counts: boolean;
21
+ unit: {
22
+ id: string;
23
+ };
24
+ progress?: {
25
+ doneAt?: string | null;
26
+ } | null;
27
+ };
28
+ /**
29
+ * The unit the course opens on: the one learned last, on any device; else the first that counts and is not done; else
30
+ * the first. Opening on the first not done sent a learner back from where they were (2026-11-08).
31
+ */
32
+ export declare function resumeUnitId(enrollment: {
33
+ lastUnitId?: string | null;
34
+ units?: UnitRow[];
35
+ } | null | undefined): string;
36
+ /**
37
+ * The seconds the page in reading was looked at already, as the server acknowledged them - a learner coming back to the
38
+ * page carries on from there. They belong to the furthest page reached, and only while it is the page left on.
39
+ */
40
+ export declare function pageLookedOf(progress: {
41
+ pagesSeen?: number | null;
42
+ pageAt?: number | null;
43
+ pageSeconds?: number | null;
44
+ } | null | undefined): number;
45
+ /**
46
+ * The progress a course screen starts each lesson from (2026-11-08). The server's answer to each tick is kept by unit;
47
+ * a lesson opened - the same unit coming back too - starts from the newest of that and what the page read. Taken once
48
+ * per lesson shown: the lesson counts its own time on from there, and a newer value given while it plays would count
49
+ * that time twice. `reopen` lets the next lesson shown take the newest again.
50
+ */
51
+ export declare class LessonStarts {
52
+ private saved;
53
+ private starts;
54
+ /** The server's answer for a unit of an enrolment. */
55
+ keep(enrollmentId: string, unitId: string, progress: any): void;
56
+ /** Where the lesson of a unit starts - fixed until `reopen`. */
57
+ startOf(enrollmentId: string, unitId: string, read: any): any;
58
+ /** The lesson shown has left - the next one starts from the newest answer. */
59
+ reopen(): void;
60
+ }
61
+ export {};
@@ -0,0 +1,85 @@
1
+ /* The fields the screens of this module read - one place, so a page and a popup ask for the same shape. */
2
+ export const UNIT_FIELDS = `id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype }`;
3
+ /** One unit's progress as the server keeps it - where it stands and the time it acknowledged. */
4
+ export const PROGRESS_FIELDS = `learnedSeconds watchedSeconds positionSeconds pagesSeen pagesRead pageSeconds pageAt doneAt`;
5
+ export const LEARNING_FIELDS = `
6
+ id status state dueOn startedAt learnedSeconds score attempts attemptsLeft quizSize missing acknowledgedAt completedAt certificateNo
7
+ course { id name description category }
8
+ version { id number minSeconds acknowledgement passScore questionCount attemptLimit }
9
+ units { counts unit { ${UNIT_FIELDS} } progress { ${PROGRESS_FIELDS} } }
10
+ lastUnitId
11
+ `;
12
+ export const MY_LIST_FIELDS = `
13
+ id status state dueOn completedAt certificateNo
14
+ course { id name category }
15
+ units { counts progress { doneAt } }
16
+ `;
17
+ export const VERSION_FIELDS = `
18
+ id number status minSeconds acknowledgement passScore questionCount attemptLimit sourceKind sourceNote publishedAt closedAt
19
+ units { ${UNIT_FIELDS} }
20
+ questions { id seq kind text explanation drafted choices { id text correct } }
21
+ `;
22
+ export const COURSE_FIELDS = `
23
+ id name description category updatedAt
24
+ manager { id name }
25
+ publishedVersion { id number }
26
+ versions { id number status publishedAt closedAt }
27
+ `;
28
+ export const STATUS_ROW_FIELDS = `
29
+ id state departmentName units unitsDone versionNumber
30
+ user { id name email }
31
+ course { id name }
32
+ enrollment { id status dueOn sourceKind startedAt completedAt learnedSeconds score attempts certificateNo cancelReason }
33
+ `;
34
+ /** Units done of those that count, from an enrolment's units. */
35
+ export function unitsDoneOf(units = []) {
36
+ const counted = units.filter(one => one.counts);
37
+ return { done: counted.filter(one => one.progress?.doneAt).length, total: counted.length };
38
+ }
39
+ /**
40
+ * The unit the course opens on: the one learned last, on any device; else the first that counts and is not done; else
41
+ * the first. Opening on the first not done sent a learner back from where they were (2026-11-08).
42
+ */
43
+ export function resumeUnitId(enrollment) {
44
+ const units = enrollment?.units ?? [];
45
+ const last = units.find(row => row.unit.id === enrollment?.lastUnitId);
46
+ return (last ?? units.find(row => row.counts && !row.progress?.doneAt) ?? units[0])?.unit.id ?? '';
47
+ }
48
+ /**
49
+ * The seconds the page in reading was looked at already, as the server acknowledged them - a learner coming back to the
50
+ * page carries on from there. They belong to the furthest page reached, and only while it is the page left on.
51
+ */
52
+ export function pageLookedOf(progress) {
53
+ if (!progress?.pageAt || progress.pageAt !== progress.pagesSeen)
54
+ return 0;
55
+ return progress.pageSeconds ?? 0;
56
+ }
57
+ /**
58
+ * The progress a course screen starts each lesson from (2026-11-08). The server's answer to each tick is kept by unit;
59
+ * a lesson opened - the same unit coming back too - starts from the newest of that and what the page read. Taken once
60
+ * per lesson shown: the lesson counts its own time on from there, and a newer value given while it plays would count
61
+ * that time twice. `reopen` lets the next lesson shown take the newest again.
62
+ */
63
+ export class LessonStarts {
64
+ constructor() {
65
+ this.saved = new Map();
66
+ this.starts = new Map();
67
+ }
68
+ /** The server's answer for a unit of an enrolment. */
69
+ keep(enrollmentId, unitId, progress) {
70
+ if (progress)
71
+ this.saved.set(`${enrollmentId}:${unitId}`, progress);
72
+ }
73
+ /** Where the lesson of a unit starts - fixed until `reopen`. */
74
+ startOf(enrollmentId, unitId, read) {
75
+ const key = `${enrollmentId}:${unitId}`;
76
+ if (!this.starts.has(key))
77
+ this.starts.set(key, this.saved.get(key) ?? read ?? {});
78
+ return this.starts.get(key);
79
+ }
80
+ /** The lesson shown has left - the next one starts from the newest answer. */
81
+ reopen() {
82
+ this.starts.clear();
83
+ }
84
+ }
85
+ //# sourceMappingURL=learning-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learning-api.js","sourceRoot":"","sources":["../../client/utils/learning-api.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAE3G,MAAM,CAAC,MAAM,WAAW,GAAG,kHAAkH,CAAA;AAE7I,iGAAiG;AACjG,MAAM,CAAC,MAAM,eAAe,GAAG,6FAA6F,CAAA;AAE5H,MAAM,CAAC,MAAM,eAAe,GAAG;;;;0BAIL,WAAW,iBAAiB,eAAe;;CAEpE,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG;;;;CAI7B,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG;;YAElB,WAAW;;CAEtB,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;CAK5B,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;CAKhC,CAAA;AAED,iEAAiE;AACjE,MAAM,UAAU,WAAW,CAAC,QAA6E,EAAE;IACzG,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;AAC5F,CAAC;AAID;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,UAAgF;IAC3G,MAAM,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,EAAE,CAAA;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,UAAU,EAAE,UAAU,CAAC,CAAA;IACtE,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,CAAA;AACpG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,QAA+G;IAC1I,IAAI,CAAC,QAAQ,EAAE,MAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,SAAS;QAAE,OAAO,CAAC,CAAA;IACzE,OAAO,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAA;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,YAAY;IAAzB;QACU,UAAK,GAAG,IAAI,GAAG,EAAe,CAAA;QAC9B,WAAM,GAAG,IAAI,GAAG,EAAe,CAAA;IAkBzC,CAAC;IAhBC,sDAAsD;IACtD,IAAI,CAAC,YAAoB,EAAE,MAAc,EAAE,QAAa;QACtD,IAAI,QAAQ;YAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,YAAY,IAAI,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAA;IACrE,CAAC;IAED,gEAAgE;IAChE,OAAO,CAAC,YAAoB,EAAE,MAAc,EAAE,IAAS;QACrD,MAAM,GAAG,GAAG,GAAG,YAAY,IAAI,MAAM,EAAE,CAAA;QACvC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,CAAA;QAClF,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC7B,CAAC;IAED,8EAA8E;IAC9E,MAAM;QACJ,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;IACrB,CAAC;CACF","sourcesContent":["/* The fields the screens of this module read - one place, so a page and a popup ask for the same shape. */\n\nexport const UNIT_FIELDS = `id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype }`\n\n/** One unit's progress as the server keeps it - where it stands and the time it acknowledged. */\nexport const PROGRESS_FIELDS = `learnedSeconds watchedSeconds positionSeconds pagesSeen pagesRead pageSeconds pageAt doneAt`\n\nexport const LEARNING_FIELDS = `\n id status state dueOn startedAt learnedSeconds score attempts attemptsLeft quizSize missing acknowledgedAt completedAt certificateNo\n course { id name description category }\n version { id number minSeconds acknowledgement passScore questionCount attemptLimit }\n units { counts unit { ${UNIT_FIELDS} } progress { ${PROGRESS_FIELDS} } }\n lastUnitId\n`\n\nexport const MY_LIST_FIELDS = `\n id status state dueOn completedAt certificateNo\n course { id name category }\n units { counts progress { doneAt } }\n`\n\nexport const VERSION_FIELDS = `\n id number status minSeconds acknowledgement passScore questionCount attemptLimit sourceKind sourceNote publishedAt closedAt\n units { ${UNIT_FIELDS} }\n questions { id seq kind text explanation drafted choices { id text correct } }\n`\n\nexport const COURSE_FIELDS = `\n id name description category updatedAt\n manager { id name }\n publishedVersion { id number }\n versions { id number status publishedAt closedAt }\n`\n\nexport const STATUS_ROW_FIELDS = `\n id state departmentName units unitsDone versionNumber\n user { id name email }\n course { id name }\n enrollment { id status dueOn sourceKind startedAt completedAt learnedSeconds score attempts certificateNo cancelReason }\n`\n\n/** Units done of those that count, from an enrolment's units. */\nexport function unitsDoneOf(units: { counts: boolean; progress?: { doneAt?: string | null } | null }[] = []) {\n const counted = units.filter(one => one.counts)\n return { done: counted.filter(one => one.progress?.doneAt).length, total: counted.length }\n}\n\ntype UnitRow = { counts: boolean; unit: { id: string }; progress?: { doneAt?: string | null } | null }\n\n/**\n * The unit the course opens on: the one learned last, on any device; else the first that counts and is not done; else\n * the first. Opening on the first not done sent a learner back from where they were (2026-11-08).\n */\nexport function resumeUnitId(enrollment: { lastUnitId?: string | null; units?: UnitRow[] } | null | undefined): string {\n const units = enrollment?.units ?? []\n const last = units.find(row => row.unit.id === enrollment?.lastUnitId)\n return (last ?? units.find(row => row.counts && !row.progress?.doneAt) ?? units[0])?.unit.id ?? ''\n}\n\n/**\n * The seconds the page in reading was looked at already, as the server acknowledged them - a learner coming back to the\n * page carries on from there. They belong to the furthest page reached, and only while it is the page left on.\n */\nexport function pageLookedOf(progress: { pagesSeen?: number | null; pageAt?: number | null; pageSeconds?: number | null } | null | undefined): number {\n if (!progress?.pageAt || progress.pageAt !== progress.pagesSeen) return 0\n return progress.pageSeconds ?? 0\n}\n\n/**\n * The progress a course screen starts each lesson from (2026-11-08). The server's answer to each tick is kept by unit;\n * a lesson opened - the same unit coming back too - starts from the newest of that and what the page read. Taken once\n * per lesson shown: the lesson counts its own time on from there, and a newer value given while it plays would count\n * that time twice. `reopen` lets the next lesson shown take the newest again.\n */\nexport class LessonStarts {\n private saved = new Map<string, any>()\n private starts = new Map<string, any>()\n\n /** The server's answer for a unit of an enrolment. */\n keep(enrollmentId: string, unitId: string, progress: any) {\n if (progress) this.saved.set(`${enrollmentId}:${unitId}`, progress)\n }\n\n /** Where the lesson of a unit starts - fixed until `reopen`. */\n startOf(enrollmentId: string, unitId: string, read: any): any {\n const key = `${enrollmentId}:${unitId}`\n if (!this.starts.has(key)) this.starts.set(key, this.saved.get(key) ?? read ?? {})\n return this.starts.get(key)\n }\n\n /** The lesson shown has left - the next one starts from the newest answer. */\n reopen() {\n this.starts.clear()\n }\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /** A word of this module - its translations, the key printed only when none is found. */
2
+ export declare const t: (key: string, options?: Record<string, unknown>) => string;
3
+ export declare const STATES: readonly ["ASSIGNED", "IN_PROGRESS", "COMPLETED", "OVERDUE", "CANCELLED"];
4
+ export declare const stateWord: (state?: string | null) => string;
5
+ export declare const CATEGORIES: readonly ["MANDATORY", "ONBOARDING", "JOB", "PRODUCT", "RULES", "OTHER"];
6
+ export declare const categoryWord: (category?: string | null) => string;
7
+ export declare const UNIT_KINDS: readonly ["VIDEO", "PDF", "MARKDOWN", "LINK"];
8
+ export declare const unitKindWord: (kind?: string | null) => string;
9
+ export declare const QUESTION_KINDS: readonly ["SINGLE", "MULTIPLE", "TRUE_FALSE"];
10
+ export declare const questionKindWord: (kind?: string | null) => string;
11
+ export declare const VERSION_STATUSES: readonly ["DRAFT", "PUBLISHED", "CLOSED"];
12
+ export declare const versionStatusWord: (status?: string | null) => string;
13
+ export declare const SOURCE_KINDS: readonly ["PUBLIC", "OWN", "PURCHASED"];
14
+ export declare const sourceKindWord: (kind?: string | null) => string;
15
+ /** What still stands before completion, said as the learner reads it. */
16
+ export declare const missingWord: (missing: string) => string;
17
+ /** Seconds as whole minutes - a learner reads time in minutes. */
18
+ export declare const minutesOf: (seconds?: number | null) => number;
19
+ /** A moment as the day it fell on, in the browser's time. */
20
+ export declare const dayOf: (at?: string | Date | null) => string;
@@ -0,0 +1,22 @@
1
+ import { i18next } from '@operato/i18n';
2
+ /** A word of this module - its translations, the key printed only when none is found. */
3
+ export const t = (key, options = {}) => i18next.t(key, options);
4
+ export const STATES = ['ASSIGNED', 'IN_PROGRESS', 'COMPLETED', 'OVERDUE', 'CANCELLED'];
5
+ export const stateWord = (state) => (state ? t(`text.learning-state-${state.toLowerCase().replace(/_/g, '-')}`) : '');
6
+ export const CATEGORIES = ['MANDATORY', 'ONBOARDING', 'JOB', 'PRODUCT', 'RULES', 'OTHER'];
7
+ export const categoryWord = (category) => (category ? t(`text.learning-category-${category.toLowerCase()}`) : '');
8
+ export const UNIT_KINDS = ['VIDEO', 'PDF', 'MARKDOWN', 'LINK'];
9
+ export const unitKindWord = (kind) => (kind ? t(`text.learning-unit-${kind.toLowerCase()}`) : '');
10
+ export const QUESTION_KINDS = ['SINGLE', 'MULTIPLE', 'TRUE_FALSE'];
11
+ export const questionKindWord = (kind) => (kind ? t(`text.learning-question-${kind.toLowerCase().replace(/_/g, '-')}`) : '');
12
+ export const VERSION_STATUSES = ['DRAFT', 'PUBLISHED', 'CLOSED'];
13
+ export const versionStatusWord = (status) => (status ? t(`text.learning-version-${status.toLowerCase()}`) : '');
14
+ export const SOURCE_KINDS = ['PUBLIC', 'OWN', 'PURCHASED'];
15
+ export const sourceKindWord = (kind) => (kind ? t(`text.learning-source-${kind.toLowerCase()}`) : '');
16
+ /** What still stands before completion, said as the learner reads it. */
17
+ export const missingWord = (missing) => t(`text.learning-missing-${missing}`);
18
+ /** Seconds as whole minutes - a learner reads time in minutes. */
19
+ export const minutesOf = (seconds) => Math.floor((seconds ?? 0) / 60);
20
+ /** A moment as the day it fell on, in the browser's time. */
21
+ export const dayOf = (at) => (at ? new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(at)) : '');
22
+ //# sourceMappingURL=learning-words.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learning-words.js","sourceRoot":"","sources":["../../client/utils/learning-words.ts"],"names":[],"mappings":"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","sourcesContent":["import { i18next } from '@operato/i18n'\n\n/** A word of this module - its translations, the key printed only when none is found. */\nexport const t = (key: string, options: Record<string, unknown> = {}) => i18next.t(key, options) as string\n\nexport const STATES = ['ASSIGNED', 'IN_PROGRESS', 'COMPLETED', 'OVERDUE', 'CANCELLED'] as const\nexport const stateWord = (state?: string | null) => (state ? t(`text.learning-state-${state.toLowerCase().replace(/_/g, '-')}`) : '')\n\nexport const CATEGORIES = ['MANDATORY', 'ONBOARDING', 'JOB', 'PRODUCT', 'RULES', 'OTHER'] as const\nexport const categoryWord = (category?: string | null) => (category ? t(`text.learning-category-${category.toLowerCase()}`) : '')\n\nexport const UNIT_KINDS = ['VIDEO', 'PDF', 'MARKDOWN', 'LINK'] as const\nexport const unitKindWord = (kind?: string | null) => (kind ? t(`text.learning-unit-${kind.toLowerCase()}`) : '')\n\nexport const QUESTION_KINDS = ['SINGLE', 'MULTIPLE', 'TRUE_FALSE'] as const\nexport const questionKindWord = (kind?: string | null) => (kind ? t(`text.learning-question-${kind.toLowerCase().replace(/_/g, '-')}`) : '')\n\nexport const VERSION_STATUSES = ['DRAFT', 'PUBLISHED', 'CLOSED'] as const\nexport const versionStatusWord = (status?: string | null) => (status ? t(`text.learning-version-${status.toLowerCase()}`) : '')\n\nexport const SOURCE_KINDS = ['PUBLIC', 'OWN', 'PURCHASED'] as const\nexport const sourceKindWord = (kind?: string | null) => (kind ? t(`text.learning-source-${kind.toLowerCase()}`) : '')\n\n/** What still stands before completion, said as the learner reads it. */\nexport const missingWord = (missing: string) => t(`text.learning-missing-${missing}`)\n\n/** Seconds as whole minutes - a learner reads time in minutes. */\nexport const minutesOf = (seconds?: number | null) => Math.floor((seconds ?? 0) / 60)\n\n/** A moment as the day it fell on, in the browser's time. */\nexport const dayOf = (at?: string | Date | null) => (at ? new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(at)) : '')\n"]}
@@ -0,0 +1,12 @@
1
+ export type MaterialPart = {
2
+ id: string;
3
+ text?: string;
4
+ image?: string;
5
+ };
6
+ export type MaterialSource = {
7
+ fileId: string;
8
+ parts: MaterialPart[];
9
+ };
10
+ /** Complete captions, never a guessed transcript from the video's name or a few sampled frames. */
11
+ export declare function captionParts(text: string): MaterialPart[];
12
+ export declare function pdfMaterial(fileId: string, url: string, signal: AbortSignal): Promise<MaterialSource>;
@@ -0,0 +1,77 @@
1
+ import { drawPdfPage, loadPdfjs, isScannedPage, pdfPageText } from '@things-factory/ai-assistant/dist-client/index.js';
2
+ /** Complete captions, never a guessed transcript from the video's name or a few sampled frames. */
3
+ export function captionParts(text) {
4
+ const clean = text.replace(/^\uFEFF/, '').trim();
5
+ if (!clean || clean.length > 120_000)
6
+ throw new Error('learning-ai-source-limit');
7
+ // Keep timestamps and cue order as evidence. Plain transcripts are also accepted, explicitly supplied by the manager.
8
+ const blocks = clean.split(/\r?\n\s*\r?\n/).filter(s => s.trim() && s.trim() !== 'WEBVTT');
9
+ const parts = [];
10
+ let current = '';
11
+ for (const block of blocks) {
12
+ if (current && current.length + block.length > 8000) {
13
+ parts.push({ id: `caption-${parts.length + 1}`, text: current });
14
+ current = '';
15
+ }
16
+ current += (current ? '\n\n' : '') + block;
17
+ }
18
+ if (current)
19
+ parts.push({ id: `caption-${parts.length + 1}`, text: current });
20
+ if (!parts.length || parts.length > 80)
21
+ throw new Error('learning-ai-source-limit');
22
+ return parts;
23
+ }
24
+ const blobData = (blob) => new Promise((resolve, reject) => {
25
+ const reader = new FileReader();
26
+ reader.onload = () => resolve(String(reader.result));
27
+ reader.onerror = () => reject(reader.error);
28
+ reader.readAsDataURL(blob);
29
+ });
30
+ export async function pdfMaterial(fileId, url, signal) {
31
+ const response = await fetch(url, { credentials: 'include', signal });
32
+ if (!response.ok)
33
+ throw new Error('learning-ai-source-read');
34
+ const blob = await response.blob();
35
+ if (blob.size > 50 * 1024 * 1024)
36
+ throw new Error('learning-ai-source-limit');
37
+ const pdfjs = await loadPdfjs();
38
+ const loading = pdfjs.getDocument({ data: new Uint8Array(await blob.arrayBuffer()) });
39
+ const abort = () => { void loading.destroy(); };
40
+ signal.addEventListener('abort', abort, { once: true });
41
+ try {
42
+ if (signal.aborted)
43
+ throw new Error('learning-ai-source-read');
44
+ const pdf = await loading.promise;
45
+ if (!pdf.numPages || pdf.numPages > 80)
46
+ throw new Error('learning-ai-source-limit');
47
+ const parts = [];
48
+ let chars = 0, scans = 0, imageChars = 0;
49
+ for (let n = 1; n <= pdf.numPages; n++) {
50
+ if (signal.aborted)
51
+ throw new Error('learning-ai-source-read');
52
+ const page = await pdf.getPage(n);
53
+ const text = pdfPageText((await page.getTextContent()).items);
54
+ if (isScannedPage(text)) {
55
+ if (++scans > 12)
56
+ throw new Error('learning-ai-source-limit');
57
+ const image = await blobData(await drawPdfPage(pdf, n));
58
+ imageChars += image.length;
59
+ if (imageChars > 8_000_000)
60
+ throw new Error('learning-ai-source-limit');
61
+ parts.push({ id: `page-${n}`, image });
62
+ }
63
+ else {
64
+ chars += text.length;
65
+ if (chars > 120_000)
66
+ throw new Error('learning-ai-source-limit');
67
+ parts.push({ id: `page-${n}`, text });
68
+ }
69
+ }
70
+ return { fileId, parts };
71
+ }
72
+ finally {
73
+ signal.removeEventListener('abort', abort);
74
+ await loading.destroy();
75
+ }
76
+ }
77
+ //# sourceMappingURL=material-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"material-source.js","sourceRoot":"","sources":["../../client/utils/material-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { drawPdfPage, loadPdfjs, isScannedPage, pdfPageText } from '@things-factory/ai-assistant/dist-client/index.js'\n\nexport type MaterialPart = { id: string; text?: string; image?: string }\nexport type MaterialSource = { fileId: string; parts: MaterialPart[] }\n\n/** Complete captions, never a guessed transcript from the video's name or a few sampled frames. */\nexport function captionParts(text: string): MaterialPart[] {\n const clean = text.replace(/^\\uFEFF/, '').trim()\n if (!clean || clean.length > 120_000) throw new Error('learning-ai-source-limit')\n // Keep timestamps and cue order as evidence. Plain transcripts are also accepted, explicitly supplied by the manager.\n const blocks = clean.split(/\\r?\\n\\s*\\r?\\n/).filter(s => s.trim() && s.trim() !== 'WEBVTT')\n const parts: MaterialPart[] = []\n let current = ''\n for (const block of blocks) {\n if (current && current.length + block.length > 8000) { parts.push({ id: `caption-${parts.length + 1}`, text: current }); current = '' }\n current += (current ? '\\n\\n' : '') + block\n }\n if (current) parts.push({ id: `caption-${parts.length + 1}`, text: current })\n if (!parts.length || parts.length > 80) throw new Error('learning-ai-source-limit')\n return parts\n}\n\nconst blobData = (blob: Blob): Promise<string> => new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(String(reader.result))\n reader.onerror = () => reject(reader.error)\n reader.readAsDataURL(blob)\n})\n\nexport async function pdfMaterial(fileId: string, url: string, signal: AbortSignal): Promise<MaterialSource> {\n const response = await fetch(url, { credentials: 'include', signal })\n if (!response.ok) throw new Error('learning-ai-source-read')\n const blob = await response.blob()\n if (blob.size > 50 * 1024 * 1024) throw new Error('learning-ai-source-limit')\n const pdfjs = await loadPdfjs()\n const loading = pdfjs.getDocument({ data: new Uint8Array(await blob.arrayBuffer()) })\n const abort = () => { void loading.destroy() }\n signal.addEventListener('abort', abort, { once: true })\n try {\n if (signal.aborted) throw new Error('learning-ai-source-read')\n const pdf = await loading.promise\n if (!pdf.numPages || pdf.numPages > 80) throw new Error('learning-ai-source-limit')\n const parts: MaterialPart[] = []\n let chars = 0, scans = 0, imageChars = 0\n for (let n = 1; n <= pdf.numPages; n++) {\n if (signal.aborted) throw new Error('learning-ai-source-read')\n const page = await pdf.getPage(n)\n const text = pdfPageText((await page.getTextContent()).items)\n if (isScannedPage(text)) {\n if (++scans > 12) throw new Error('learning-ai-source-limit')\n const image = await blobData(await drawPdfPage(pdf, n))\n imageChars += image.length\n if (imageChars > 8_000_000) throw new Error('learning-ai-source-limit')\n parts.push({ id: `page-${n}`, image })\n } else {\n chars += text.length\n if (chars > 120_000) throw new Error('learning-ai-source-limit')\n parts.push({ id: `page-${n}`, text })\n }\n }\n return { fileId, parts }\n } finally { signal.removeEventListener('abort', abort); await loading.destroy() }\n}\n"]}
@@ -0,0 +1,38 @@
1
+ export declare const LEARNING_COURSE_ACTIVITY = "Learning Course";
2
+ export declare const LEARNING_ACTIVITIES: {
3
+ name: string;
4
+ label: string;
5
+ description: string;
6
+ guide: string[];
7
+ release: string;
8
+ provider: string;
9
+ category: string;
10
+ activityType: string;
11
+ priority: number;
12
+ startingType: string;
13
+ multiple: string;
14
+ uiType: string;
15
+ uiSource: string;
16
+ searchKeys: {
17
+ name: string;
18
+ description: string;
19
+ inputKey: string;
20
+ tKey: string;
21
+ }[];
22
+ viewType: string;
23
+ viewSource: string;
24
+ model: {
25
+ name: string;
26
+ description: string;
27
+ tag: string;
28
+ hidden: boolean;
29
+ mandatory: boolean;
30
+ inout: string;
31
+ type: string;
32
+ options: {};
33
+ unit: any;
34
+ quantifier: number[];
35
+ spec: {};
36
+ }[];
37
+ standardTime: number;
38
+ }[];
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LEARNING_ACTIVITIES = exports.LEARNING_COURSE_ACTIVITY = void 0;
4
+ /*
5
+ * 수강 (ADR-0194 decision 5) - not an approval: one person is asked to take one course by a day. It shows the course and
6
+ * the day, with the way to the course (`learning-todo`). Completing the course ends it as done; a withdrawn or cancelled
7
+ * enrolment aborts it. Reminding one past its day is worklist's (ADR-0186).
8
+ *
9
+ * Data only. An application installs it into its domains (Office `REQUIRED_ACTIVITIES`).
10
+ */
11
+ exports.LEARNING_COURSE_ACTIVITY = 'Learning Course';
12
+ exports.LEARNING_ACTIVITIES = [
13
+ {
14
+ name: exports.LEARNING_COURSE_ACTIVITY,
15
+ label: 'course learning',
16
+ description: 'A course to take by a day',
17
+ guide: [
18
+ 'an enrolment hands it out - a mandatory training session, onboarding, or a manager assigning a course.',
19
+ 'it opens the course; completing the course ends it.',
20
+ 'a to-do past its day is reminded as every overdue to-do is.'
21
+ ],
22
+ release: '1.0.0',
23
+ provider: 'hatiolab.com',
24
+ category: 'learning',
25
+ activityType: 'user',
26
+ priority: 2,
27
+ startingType: 'issue',
28
+ multiple: 'single',
29
+ uiType: 'custom-element',
30
+ uiSource: 'learning-todo',
31
+ searchKeys: [{ name: 'headline', description: 'The course and its due day', inputKey: 'headline', tKey: 'field.subject' }],
32
+ viewType: '',
33
+ viewSource: '',
34
+ model: [
35
+ { name: 'enrollmentId', description: 'The enrolment', tag: 'enrollmentId', hidden: true, mandatory: true, inout: 'in', type: 'string', options: {}, unit: null, quantifier: [1], spec: {} },
36
+ { name: 'headline', description: 'The course and its due day', tag: 'headline', hidden: true, mandatory: true, inout: 'in', type: 'string', options: {}, unit: null, quantifier: [1], spec: {} },
37
+ { name: 'address', description: 'The course screen', tag: 'address', hidden: true, mandatory: true, inout: 'in', type: 'string', options: {}, unit: null, quantifier: [1], spec: {} }
38
+ ],
39
+ standardTime: 14 * 24 * 60 * 60
40
+ }
41
+ ];
42
+ //# sourceMappingURL=activities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activities.js","sourceRoot":"","sources":["../server/activities.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACU,QAAA,wBAAwB,GAAG,iBAAiB,CAAA;AAE5C,QAAA,mBAAmB,GAAG;IACjC;QACE,IAAI,EAAE,gCAAwB;QAC9B,KAAK,EAAE,iBAAiB;QACxB,WAAW,EAAE,2BAA2B;QACxC,KAAK,EAAE;YACL,wGAAwG;YACxG,qDAAqD;YACrD,6DAA6D;SAC9D;QACD,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,cAAc;QACxB,QAAQ,EAAE,UAAU;QACpB,YAAY,EAAE,MAAM;QACpB,QAAQ,EAAE,CAAC;QACX,YAAY,EAAE,OAAO;QACrB,QAAQ,EAAE,QAAQ;QAClB,MAAM,EAAE,gBAAgB;QACxB,QAAQ,EAAE,eAAe;QACzB,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,4BAA4B,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;QAC1H,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,EAAE;QACd,KAAK,EAAE;YACL,EAAE,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;YAC3L,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,4BAA4B,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;YAChM,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,mBAAmB,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;SACtL;QACD,YAAY,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;KAChC;CACF,CAAA","sourcesContent":["/*\n * 수강 (ADR-0194 decision 5) - not an approval: one person is asked to take one course by a day. It shows the course and\n * the day, with the way to the course (`learning-todo`). Completing the course ends it as done; a withdrawn or cancelled\n * enrolment aborts it. Reminding one past its day is worklist's (ADR-0186).\n *\n * Data only. An application installs it into its domains (Office `REQUIRED_ACTIVITIES`).\n */\nexport const LEARNING_COURSE_ACTIVITY = 'Learning Course'\n\nexport const LEARNING_ACTIVITIES = [\n {\n name: LEARNING_COURSE_ACTIVITY,\n label: 'course learning',\n description: 'A course to take by a day',\n guide: [\n 'an enrolment hands it out - a mandatory training session, onboarding, or a manager assigning a course.',\n 'it opens the course; completing the course ends it.',\n 'a to-do past its day is reminded as every overdue to-do is.'\n ],\n release: '1.0.0',\n provider: 'hatiolab.com',\n category: 'learning',\n activityType: 'user',\n priority: 2,\n startingType: 'issue',\n multiple: 'single',\n uiType: 'custom-element',\n uiSource: 'learning-todo',\n searchKeys: [{ name: 'headline', description: 'The course and its due day', inputKey: 'headline', tKey: 'field.subject' }],\n viewType: '',\n viewSource: '',\n model: [\n { name: 'enrollmentId', description: 'The enrolment', tag: 'enrollmentId', hidden: true, mandatory: true, inout: 'in', type: 'string', options: {}, unit: null, quantifier: [1], spec: {} },\n { name: 'headline', description: 'The course and its due day', tag: 'headline', hidden: true, mandatory: true, inout: 'in', type: 'string', options: {}, unit: null, quantifier: [1], spec: {} },\n { name: 'address', description: 'The course screen', tag: 'address', hidden: true, mandatory: true, inout: 'in', type: 'string', options: {}, unit: null, quantifier: [1], spec: {} }\n ],\n standardTime: 14 * 24 * 60 * 60\n }\n]\n"]}
@@ -0,0 +1,9 @@
1
+ import { type DetailRead, type EntryTarget, type ListRead } from '@things-factory/ai-assistant';
2
+ export declare const LEARNING_ENROLLMENTS_READ: ListRead;
3
+ export declare const LEARNING_CONTENT_READ: DetailRead;
4
+ export declare const QUESTION_DRAFT_TARGET: EntryTarget;
5
+ export declare function registerLearningReads(): void;
6
+ /** The keys an application's assistant mode names to read learning. */
7
+ export declare const LEARNING_READ_TARGETS: string[];
8
+ /** The keys an application's assistant mode names to let the assistant enter learning records. */
9
+ export declare const LEARNING_ENTRY_TARGETS: string[];
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LEARNING_ENTRY_TARGETS = exports.LEARNING_READ_TARGETS = exports.QUESTION_DRAFT_TARGET = exports.LEARNING_CONTENT_READ = exports.LEARNING_ENROLLMENTS_READ = void 0;
4
+ exports.registerLearningReads = registerLearningReads;
5
+ /*
6
+ * The assistant's reads and entry of learning (ADR-0194 decision 12) - registered at load; an application names the keys
7
+ * in its assistant mode's `readTargets`.
8
+ */
9
+ const ai_assistant_1 = require("@things-factory/ai-assistant");
10
+ const learning_ai_1 = require("./service/enrollment/learning-ai");
11
+ exports.LEARNING_ENROLLMENTS_READ = {
12
+ key: 'learning.enrollments',
13
+ title: 'title.learning-enrollment-status',
14
+ name: 'Course enrolments (수강) - who is to take which course by when and where each stands: ASSIGNED (not started), IN_PROGRESS, COMPLETED or OVERDUE (open past its due day), with the days left to the due day. A manager reads everyone, a department head their people, anyone else their own.',
15
+ listQuery: 'learningEnrollments',
16
+ selection: 'id learner departmentName course state dueOn daysLeft startedAt completedAt unitsDone units',
17
+ columns: [
18
+ { name: 'learner', type: 'string', labelKey: 'field.learning-learner' },
19
+ { name: 'departmentName', type: 'string', labelKey: 'field.learning-department' },
20
+ { name: 'course', type: 'string', labelKey: 'field.learning-course-name' },
21
+ { name: 'state', type: 'string', labelKey: 'field.learning-state' },
22
+ { name: 'dueOn', type: 'date', labelKey: 'field.learning-due-on' },
23
+ { name: 'daysLeft', type: 'number', labelKey: 'field.learning-days-left' },
24
+ { name: 'completedAt', type: 'date', labelKey: 'field.learning-completed-on' },
25
+ { name: 'unitsDone', type: 'number', labelKey: 'field.learning-units-done' }
26
+ ],
27
+ status: { field: 'state', values: ['ASSIGNED', 'IN_PROGRESS', 'COMPLETED', 'OVERDUE'] },
28
+ search: "learner's name or course",
29
+ route: 'learning-enrollment-status'
30
+ };
31
+ exports.LEARNING_CONTENT_READ = {
32
+ key: 'learning.course-content',
33
+ labelKey: 'title.learning-units',
34
+ description: "The units of one course version - names and texts - to answer a question on the course the person is taking (give their enrollmentId; only that course's version, refused while their quiz attempt is open - do not help with the quiz) or, for a manager, to draft questions of a version (give versionId). Answer only from these units.",
35
+ query: 'query ($enrollmentId: String, $versionId: String) { learningContent(enrollmentId: $enrollmentId, versionId: $versionId) { course version units { seq kind name text } } }',
36
+ args: [
37
+ { name: 'enrollmentId', type: 'string', axis: 'enrollment', description: 'The enrolment open on the course screen (learning-view?id=...)' },
38
+ { name: 'versionId', type: 'string', axis: 'course-version', description: 'A version open in the course editor - managers only' }
39
+ ]
40
+ };
41
+ exports.QUESTION_DRAFT_TARGET = {
42
+ key: 'learning.question-draft',
43
+ labelKey: 'title.learning-questions',
44
+ description: 'A quiz question drafted from the units of a DRAFT course version (read learning.course-content with its versionId first). It is entered marked as an AI draft; a manager reviews and saves it before the version can be published. Only a draft version takes it.',
45
+ fields: [
46
+ { name: 'versionId', type: 'string', required: true, axis: 'course-version', description: 'The draft version the question belongs to', labelKey: 'title.learning-versions' },
47
+ { name: 'kind', type: 'enum', required: true, values: ['SINGLE', 'MULTIPLE', 'TRUE_FALSE'], valueKeyPrefix: 'text.learning-question-', labelKey: 'field.learning-question-kind' },
48
+ { name: 'text', type: 'text', required: true, description: 'What it asks - from the units only', labelKey: 'field.learning-question-text' },
49
+ { name: 'choices', type: 'text', required: true, description: 'One choice a line; start each right choice with *. TRUE_FALSE has two lines.', labelKey: 'field.learning-question-choices' },
50
+ { name: 'explanation', type: 'text', description: 'Why the answer is right, citing the unit', labelKey: 'field.learning-question-explanation' }
51
+ ],
52
+ mutation: 'mutation ($versionId: String!, $question: CourseQuestionInput!) { addDraftedCourseQuestion(versionId: $versionId, question: $question) { id } }',
53
+ variables: row => ({
54
+ versionId: row.values.versionId,
55
+ question: { kind: row.values.kind, text: row.values.text, choices: (0, learning_ai_1.choicesOfText)(String(row.values.choices ?? '')), explanation: row.values.explanation ?? null }
56
+ }),
57
+ appliedOf: (data, row) => ({ id: data?.addDraftedCourseQuestion?.id, label: String(row.values.text ?? '').slice(0, 60) }),
58
+ screen: 'learning-course-editor',
59
+ maxRows: 30
60
+ };
61
+ function registerLearningReads() {
62
+ (0, ai_assistant_1.registerReadTarget)((0, ai_assistant_1.listReadTarget)(exports.LEARNING_ENROLLMENTS_READ));
63
+ (0, ai_assistant_1.registerDetailRead)(exports.LEARNING_CONTENT_READ);
64
+ (0, ai_assistant_1.registerEntryTarget)(exports.QUESTION_DRAFT_TARGET);
65
+ }
66
+ /** The keys an application's assistant mode names to read learning. */
67
+ exports.LEARNING_READ_TARGETS = [exports.LEARNING_ENROLLMENTS_READ.key, exports.LEARNING_CONTENT_READ.key];
68
+ /** The keys an application's assistant mode names to let the assistant enter learning records. */
69
+ exports.LEARNING_ENTRY_TARGETS = [exports.QUESTION_DRAFT_TARGET.key];
70
+ //# sourceMappingURL=ai-reads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-reads.js","sourceRoot":"","sources":["../server/ai-reads.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The assistant's reads and entry of learning (ADR-0194 decision 12) - registered at load; an application names the keys\n * in its assistant mode's `readTargets`.\n */\nimport { listReadTarget, registerDetailRead, registerEntryTarget, registerReadTarget, type DetailRead, type EntryTarget, type ListRead } from '@things-factory/ai-assistant'\n\nimport { choicesOfText } from './service/enrollment/learning-ai'\n\nexport const LEARNING_ENROLLMENTS_READ: ListRead = {\n key: 'learning.enrollments',\n title: 'title.learning-enrollment-status',\n name: 'Course enrolments (수강) - who is to take which course by when and where each stands: ASSIGNED (not started), IN_PROGRESS, COMPLETED or OVERDUE (open past its due day), with the days left to the due day. A manager reads everyone, a department head their people, anyone else their own.',\n listQuery: 'learningEnrollments',\n selection: 'id learner departmentName course state dueOn daysLeft startedAt completedAt unitsDone units',\n columns: [\n { name: 'learner', type: 'string', labelKey: 'field.learning-learner' },\n { name: 'departmentName', type: 'string', labelKey: 'field.learning-department' },\n { name: 'course', type: 'string', labelKey: 'field.learning-course-name' },\n { name: 'state', type: 'string', labelKey: 'field.learning-state' },\n { name: 'dueOn', type: 'date', labelKey: 'field.learning-due-on' },\n { name: 'daysLeft', type: 'number', labelKey: 'field.learning-days-left' },\n { name: 'completedAt', type: 'date', labelKey: 'field.learning-completed-on' },\n { name: 'unitsDone', type: 'number', labelKey: 'field.learning-units-done' }\n ],\n status: { field: 'state', values: ['ASSIGNED', 'IN_PROGRESS', 'COMPLETED', 'OVERDUE'] },\n search: \"learner's name or course\",\n route: 'learning-enrollment-status'\n}\n\nexport const LEARNING_CONTENT_READ: DetailRead = {\n key: 'learning.course-content',\n labelKey: 'title.learning-units',\n description:\n \"The units of one course version - names and texts - to answer a question on the course the person is taking (give their enrollmentId; only that course's version, refused while their quiz attempt is open - do not help with the quiz) or, for a manager, to draft questions of a version (give versionId). Answer only from these units.\",\n query: 'query ($enrollmentId: String, $versionId: String) { learningContent(enrollmentId: $enrollmentId, versionId: $versionId) { course version units { seq kind name text } } }',\n args: [\n { name: 'enrollmentId', type: 'string', axis: 'enrollment', description: 'The enrolment open on the course screen (learning-view?id=...)' },\n { name: 'versionId', type: 'string', axis: 'course-version', description: 'A version open in the course editor - managers only' }\n ]\n}\n\nexport const QUESTION_DRAFT_TARGET: EntryTarget = {\n key: 'learning.question-draft',\n labelKey: 'title.learning-questions',\n description:\n 'A quiz question drafted from the units of a DRAFT course version (read learning.course-content with its versionId first). It is entered marked as an AI draft; a manager reviews and saves it before the version can be published. Only a draft version takes it.',\n fields: [\n { name: 'versionId', type: 'string', required: true, axis: 'course-version', description: 'The draft version the question belongs to', labelKey: 'title.learning-versions' },\n { name: 'kind', type: 'enum', required: true, values: ['SINGLE', 'MULTIPLE', 'TRUE_FALSE'], valueKeyPrefix: 'text.learning-question-', labelKey: 'field.learning-question-kind' },\n { name: 'text', type: 'text', required: true, description: 'What it asks - from the units only', labelKey: 'field.learning-question-text' },\n { name: 'choices', type: 'text', required: true, description: 'One choice a line; start each right choice with *. TRUE_FALSE has two lines.', labelKey: 'field.learning-question-choices' },\n { name: 'explanation', type: 'text', description: 'Why the answer is right, citing the unit', labelKey: 'field.learning-question-explanation' }\n ],\n mutation: 'mutation ($versionId: String!, $question: CourseQuestionInput!) { addDraftedCourseQuestion(versionId: $versionId, question: $question) { id } }',\n variables: row => ({\n versionId: row.values.versionId,\n question: { kind: row.values.kind, text: row.values.text, choices: choicesOfText(String(row.values.choices ?? '')), explanation: row.values.explanation ?? null }\n }),\n appliedOf: (data, row) => ({ id: data?.addDraftedCourseQuestion?.id, label: String(row.values.text ?? '').slice(0, 60) }),\n screen: 'learning-course-editor',\n maxRows: 30\n}\n\nexport function registerLearningReads(): void {\n registerReadTarget(listReadTarget(LEARNING_ENROLLMENTS_READ))\n registerDetailRead(LEARNING_CONTENT_READ)\n registerEntryTarget(QUESTION_DRAFT_TARGET)\n}\n\n/** The keys an application's assistant mode names to read learning. */\nexport const LEARNING_READ_TARGETS = [LEARNING_ENROLLMENTS_READ.key, LEARNING_CONTENT_READ.key]\n\n/** The keys an application's assistant mode names to let the assistant enter learning records. */\nexport const LEARNING_ENTRY_TARGETS = [QUESTION_DRAFT_TARGET.key]\n"]}
@@ -0,0 +1 @@
1
+ export declare function registerLearningFiles(): void;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerLearningFiles = registerLearningFiles;
4
+ /*
5
+ * Who may open a unit's file by its path (ADR-0194 decisions 3, 11) - whoever may read the unit's version
6
+ * (`mayReadVersion`: a manager, or a learner whose enrolment shows that version), the same rule the GraphQL fields ask.
7
+ * attachment-base has already checked the file is of the request's domain.
8
+ */
9
+ const attachment_base_1 = require("@things-factory/attachment-base");
10
+ const shell_1 = require("@things-factory/shell");
11
+ const course_1 = require("./service/course");
12
+ const course_unit_1 = require("./service/course/course-unit");
13
+ const course_version_1 = require("./service/course/course-version");
14
+ const course_service_1 = require("./service/course/course-service");
15
+ function registerLearningFiles() {
16
+ (0, attachment_base_1.registerAttachmentReader)(course_1.COURSE_UNIT_REF_TYPE, async (attachment, context) => {
17
+ const unit = attachment.refBy ? await (0, shell_1.getRepository)(course_unit_1.CourseUnit).findOne({ where: { id: attachment.refBy, domain: { id: context.state.domain.id } } }) : null;
18
+ if (!unit)
19
+ return false;
20
+ return await (0, course_service_1.mayReadVersion)(context, await (0, shell_1.getRepository)(course_version_1.CourseVersion).findOne({ where: { id: unit.versionId }, withDeleted: true }));
21
+ });
22
+ }
23
+ //# sourceMappingURL=attachment-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-reader.js","sourceRoot":"","sources":["../server/attachment-reader.ts"],"names":[],"mappings":";;AAaA,sDAMC;AAnBD;;;;GAIG;AACH,qEAA0E;AAC1E,iDAAqD;AAErD,6CAAuD;AACvD,8DAAyD;AACzD,oEAA+D;AAC/D,oEAAgE;AAEhE,SAAgB,qBAAqB;IACnC,IAAA,0CAAwB,EAAC,6BAAoB,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE;QAC3E,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,IAAA,qBAAa,EAAC,wBAAU,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAS,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACnK,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAA;QACvB,OAAO,MAAM,IAAA,+BAAc,EAAC,OAAO,EAAE,MAAM,IAAA,qBAAa,EAAC,8BAAa,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,SAAS,EAAS,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAC/I,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["/*\n * Who may open a unit's file by its path (ADR-0194 decisions 3, 11) - whoever may read the unit's version\n * (`mayReadVersion`: a manager, or a learner whose enrolment shows that version), the same rule the GraphQL fields ask.\n * attachment-base has already checked the file is of the request's domain.\n */\nimport { registerAttachmentReader } from '@things-factory/attachment-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { COURSE_UNIT_REF_TYPE } from './service/course'\nimport { CourseUnit } from './service/course/course-unit'\nimport { CourseVersion } from './service/course/course-version'\nimport { mayReadVersion } from './service/course/course-service'\n\nexport function registerLearningFiles(): void {\n registerAttachmentReader(COURSE_UNIT_REF_TYPE, async (attachment, context) => {\n const unit = attachment.refBy ? await getRepository(CourseUnit).findOne({ where: { id: attachment.refBy, domain: { id: context.state.domain.id } } as any }) : null\n if (!unit) return false\n return await mayReadVersion(context, await getRepository(CourseVersion).findOne({ where: { id: unit.versionId } as any, withDeleted: true }))\n })\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export * from './service';
2
+ export { LEARNING_ACTIVITIES, LEARNING_COURSE_ACTIVITY } from './activities';
3
+ export { LEARNING_ENTRY_TARGETS, LEARNING_READ_TARGETS } from './ai-reads';
4
+ export { ROLE_TASKS as LEARNING_ROLE_TASKS } from './role-tasks';
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LEARNING_ROLE_TASKS = exports.LEARNING_READ_TARGETS = exports.LEARNING_ENTRY_TARGETS = exports.LEARNING_COURSE_ACTIVITY = exports.LEARNING_ACTIVITIES = void 0;
4
+ const tslib_1 = require("tslib");
5
+ tslib_1.__exportStar(require("./service"), exports);
6
+ var activities_1 = require("./activities");
7
+ Object.defineProperty(exports, "LEARNING_ACTIVITIES", { enumerable: true, get: function () { return activities_1.LEARNING_ACTIVITIES; } });
8
+ Object.defineProperty(exports, "LEARNING_COURSE_ACTIVITY", { enumerable: true, get: function () { return activities_1.LEARNING_COURSE_ACTIVITY; } });
9
+ var ai_reads_1 = require("./ai-reads");
10
+ Object.defineProperty(exports, "LEARNING_ENTRY_TARGETS", { enumerable: true, get: function () { return ai_reads_1.LEARNING_ENTRY_TARGETS; } });
11
+ Object.defineProperty(exports, "LEARNING_READ_TARGETS", { enumerable: true, get: function () { return ai_reads_1.LEARNING_READ_TARGETS; } });
12
+ var role_tasks_1 = require("./role-tasks");
13
+ Object.defineProperty(exports, "LEARNING_ROLE_TASKS", { enumerable: true, get: function () { return role_tasks_1.ROLE_TASKS; } });
14
+ const auth_base_1 = require("@things-factory/auth-base");
15
+ const worklist_1 = require("@things-factory/worklist");
16
+ const activities_2 = require("./activities");
17
+ const ai_reads_2 = require("./ai-reads");
18
+ const attachment_reader_1 = require("./attachment-reader");
19
+ const learning_subjects_1 = require("./learning-subjects");
20
+ const role_tasks_2 = require("./role-tasks");
21
+ const learning_documents_1 = require("./service/enrollment/learning-documents");
22
+ for (const task of role_tasks_2.ROLE_TASKS)
23
+ (0, auth_base_1.registerRoleTask)(task);
24
+ (0, attachment_reader_1.registerLearningFiles)();
25
+ (0, learning_documents_1.registerLearningDocuments)();
26
+ (0, ai_reads_2.registerLearningReads)();
27
+ (0, learning_subjects_1.registerLearningSubjects)();
28
+ /*
29
+ * Importing this file registers the learner's to-do (ADR-0194 decision 5) - a bootstrap hook would be too late for
30
+ * worklist to find it. Which domains have it is the application's call: it installs `LEARNING_ACTIVITIES`.
31
+ */
32
+ activities_2.LEARNING_ACTIVITIES.forEach(template => worklist_1.ActivityInstallations.installActivityTemplate(template));
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,oDAAyB;AACzB,2CAA4E;AAAnE,iHAAA,mBAAmB,OAAA;AAAE,sHAAA,wBAAwB,OAAA;AACtD,uCAA0E;AAAjE,kHAAA,sBAAsB,OAAA;AAAE,iHAAA,qBAAqB,OAAA;AACtD,2CAAgE;AAAvD,iHAAA,UAAU,OAAuB;AAE1C,yDAA4D;AAC5D,uDAAgE;AAEhE,6CAAkD;AAClD,yCAAkD;AAClD,2DAA2D;AAC3D,2DAA8D;AAC9D,6CAAyC;AACzC,gFAAmF;AAEnF,KAAK,MAAM,IAAI,IAAI,uBAAU;IAAE,IAAA,4BAAgB,EAAC,IAAI,CAAC,CAAA;AACrD,IAAA,yCAAqB,GAAE,CAAA;AACvB,IAAA,8CAAyB,GAAE,CAAA;AAC3B,IAAA,gCAAqB,GAAE,CAAA;AACvB,IAAA,4CAAwB,GAAE,CAAA;AAE1B;;;GAGG;AACH,gCAAmB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,gCAAqB,CAAC,uBAAuB,CAAC,QAAe,CAAC,CAAC,CAAA","sourcesContent":["export * from './service'\nexport { LEARNING_ACTIVITIES, LEARNING_COURSE_ACTIVITY } from './activities'\nexport { LEARNING_ENTRY_TARGETS, LEARNING_READ_TARGETS } from './ai-reads'\nexport { ROLE_TASKS as LEARNING_ROLE_TASKS } from './role-tasks'\n\nimport { registerRoleTask } from '@things-factory/auth-base'\nimport { ActivityInstallations } from '@things-factory/worklist'\n\nimport { LEARNING_ACTIVITIES } from './activities'\nimport { registerLearningReads } from './ai-reads'\nimport { registerLearningFiles } from './attachment-reader'\nimport { registerLearningSubjects } from './learning-subjects'\nimport { ROLE_TASKS } from './role-tasks'\nimport { registerLearningDocuments } from './service/enrollment/learning-documents'\n\nfor (const task of ROLE_TASKS) registerRoleTask(task)\nregisterLearningFiles()\nregisterLearningDocuments()\nregisterLearningReads()\nregisterLearningSubjects()\n\n/*\n * Importing this file registers the learner's to-do (ADR-0194 decision 5) - a bootstrap hook would be too late for\n * worklist to find it. Which domains have it is the application's call: it installs `LEARNING_ACTIVITIES`.\n */\nLEARNING_ACTIVITIES.forEach(template => ActivityInstallations.installActivityTemplate(template as any))\n"]}