@contractspec/module.learning-journey 3.7.15 → 3.7.17
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/browser/contracts/index.js +1 -578
- package/dist/browser/contracts/models.js +1 -193
- package/dist/browser/contracts/onboarding.js +1 -417
- package/dist/browser/contracts/operations.js +1 -326
- package/dist/browser/contracts/shared.js +1 -5
- package/dist/browser/docs/index.js +7 -51
- package/dist/browser/docs/learning-journey.docblock.js +7 -51
- package/dist/browser/engines/index.js +1 -675
- package/dist/browser/engines/srs.js +1 -198
- package/dist/browser/engines/streak.js +1 -159
- package/dist/browser/engines/xp.js +1 -320
- package/dist/browser/entities/ai.js +1 -343
- package/dist/browser/entities/course.js +1 -276
- package/dist/browser/entities/flashcard.js +1 -222
- package/dist/browser/entities/gamification.js +1 -340
- package/dist/browser/entities/index.js +1 -2140
- package/dist/browser/entities/learner.js +1 -333
- package/dist/browser/entities/onboarding.js +1 -301
- package/dist/browser/entities/quiz.js +1 -304
- package/dist/browser/events.js +1 -423
- package/dist/browser/i18n/catalogs/en.js +1 -43
- package/dist/browser/i18n/catalogs/es.js +1 -43
- package/dist/browser/i18n/catalogs/fr.js +1 -43
- package/dist/browser/i18n/catalogs/index.js +1 -127
- package/dist/browser/i18n/index.js +1 -169
- package/dist/browser/i18n/keys.js +1 -16
- package/dist/browser/i18n/locale.js +1 -13
- package/dist/browser/i18n/messages.js +1 -139
- package/dist/browser/index.js +7 -3914
- package/dist/browser/learning-journey.capability.js +1 -43
- package/dist/browser/learning-journey.feature.js +1 -56
- package/dist/contracts/index.js +1 -578
- package/dist/contracts/models.js +1 -193
- package/dist/contracts/onboarding.js +1 -417
- package/dist/contracts/operations.js +1 -326
- package/dist/contracts/shared.js +1 -5
- package/dist/docs/index.js +7 -51
- package/dist/docs/learning-journey.docblock.js +7 -51
- package/dist/engines/index.js +1 -675
- package/dist/engines/srs.js +1 -198
- package/dist/engines/streak.js +1 -159
- package/dist/engines/xp.js +1 -320
- package/dist/entities/ai.js +1 -343
- package/dist/entities/course.js +1 -276
- package/dist/entities/flashcard.js +1 -222
- package/dist/entities/gamification.js +1 -340
- package/dist/entities/index.js +1 -2140
- package/dist/entities/learner.js +1 -333
- package/dist/entities/onboarding.js +1 -301
- package/dist/entities/quiz.js +1 -304
- package/dist/events.js +1 -423
- package/dist/i18n/catalogs/en.js +1 -43
- package/dist/i18n/catalogs/es.js +1 -43
- package/dist/i18n/catalogs/fr.js +1 -43
- package/dist/i18n/catalogs/index.js +1 -127
- package/dist/i18n/index.js +1 -169
- package/dist/i18n/keys.js +1 -16
- package/dist/i18n/locale.js +1 -13
- package/dist/i18n/messages.js +1 -139
- package/dist/index.js +7 -3914
- package/dist/learning-journey.capability.js +1 -43
- package/dist/learning-journey.feature.js +1 -56
- package/dist/node/contracts/index.js +1 -578
- package/dist/node/contracts/models.js +1 -193
- package/dist/node/contracts/onboarding.js +1 -417
- package/dist/node/contracts/operations.js +1 -326
- package/dist/node/contracts/shared.js +1 -5
- package/dist/node/docs/index.js +7 -51
- package/dist/node/docs/learning-journey.docblock.js +7 -51
- package/dist/node/engines/index.js +1 -675
- package/dist/node/engines/srs.js +1 -198
- package/dist/node/engines/streak.js +1 -159
- package/dist/node/engines/xp.js +1 -320
- package/dist/node/entities/ai.js +1 -343
- package/dist/node/entities/course.js +1 -276
- package/dist/node/entities/flashcard.js +1 -222
- package/dist/node/entities/gamification.js +1 -340
- package/dist/node/entities/index.js +1 -2140
- package/dist/node/entities/learner.js +1 -333
- package/dist/node/entities/onboarding.js +1 -301
- package/dist/node/entities/quiz.js +1 -304
- package/dist/node/events.js +1 -423
- package/dist/node/i18n/catalogs/en.js +1 -43
- package/dist/node/i18n/catalogs/es.js +1 -43
- package/dist/node/i18n/catalogs/fr.js +1 -43
- package/dist/node/i18n/catalogs/index.js +1 -127
- package/dist/node/i18n/index.js +1 -169
- package/dist/node/i18n/keys.js +1 -16
- package/dist/node/i18n/locale.js +1 -13
- package/dist/node/i18n/messages.js +1 -139
- package/dist/node/index.js +7 -3914
- package/dist/node/learning-journey.capability.js +1 -43
- package/dist/node/learning-journey.feature.js +1 -56
- package/package.json +7 -7
package/dist/node/index.js
CHANGED
|
@@ -1,563 +1,4 @@
|
|
|
1
|
-
// src/contracts/models.ts
|
|
2
|
-
import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
|
|
3
|
-
var CourseModel = defineSchemaModel({
|
|
4
|
-
name: "Course",
|
|
5
|
-
description: "A learning course",
|
|
6
|
-
fields: {
|
|
7
|
-
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
8
|
-
title: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
9
|
-
slug: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
10
|
-
description: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
11
|
-
difficulty: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
12
|
-
status: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
13
|
-
estimatedDuration: {
|
|
14
|
-
type: ScalarTypeEnum.Int_unsecure(),
|
|
15
|
-
isOptional: true
|
|
16
|
-
},
|
|
17
|
-
thumbnailUrl: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
18
|
-
createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
var LearnerModel = defineSchemaModel({
|
|
22
|
-
name: "Learner",
|
|
23
|
-
description: "A learner profile",
|
|
24
|
-
fields: {
|
|
25
|
-
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
26
|
-
userId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
27
|
-
displayName: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
28
|
-
level: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
29
|
-
totalXp: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
30
|
-
currentStreak: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
31
|
-
longestStreak: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
32
|
-
createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
var EnrollmentModel = defineSchemaModel({
|
|
36
|
-
name: "Enrollment",
|
|
37
|
-
description: "A course enrollment",
|
|
38
|
-
fields: {
|
|
39
|
-
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
40
|
-
learnerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
41
|
-
courseId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
42
|
-
status: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
43
|
-
progress: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
44
|
-
startedAt: { type: ScalarTypeEnum.DateTime(), isOptional: true },
|
|
45
|
-
completedAt: { type: ScalarTypeEnum.DateTime(), isOptional: true }
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
var ProgressModel = defineSchemaModel({
|
|
49
|
-
name: "LessonProgress",
|
|
50
|
-
description: "Lesson progress",
|
|
51
|
-
fields: {
|
|
52
|
-
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
53
|
-
learnerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
54
|
-
lessonId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
55
|
-
status: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
56
|
-
progress: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
57
|
-
score: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
|
|
58
|
-
timeSpent: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
59
|
-
completedAt: { type: ScalarTypeEnum.DateTime(), isOptional: true }
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
var DeckModel = defineSchemaModel({
|
|
63
|
-
name: "Deck",
|
|
64
|
-
description: "A flashcard deck",
|
|
65
|
-
fields: {
|
|
66
|
-
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
67
|
-
title: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
68
|
-
description: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
69
|
-
cardCount: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
70
|
-
isPublic: { type: ScalarTypeEnum.Boolean(), isOptional: false },
|
|
71
|
-
createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
var CardModel = defineSchemaModel({
|
|
75
|
-
name: "Card",
|
|
76
|
-
description: "A flashcard",
|
|
77
|
-
fields: {
|
|
78
|
-
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
79
|
-
deckId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
80
|
-
front: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
81
|
-
back: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
82
|
-
hints: { type: ScalarTypeEnum.JSON(), isOptional: true },
|
|
83
|
-
isDue: { type: ScalarTypeEnum.Boolean(), isOptional: false },
|
|
84
|
-
nextReviewAt: { type: ScalarTypeEnum.DateTime(), isOptional: true }
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
var AchievementModel = defineSchemaModel({
|
|
88
|
-
name: "Achievement",
|
|
89
|
-
description: "An achievement",
|
|
90
|
-
fields: {
|
|
91
|
-
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
92
|
-
key: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
93
|
-
name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
94
|
-
description: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
95
|
-
icon: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
96
|
-
xpReward: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
97
|
-
unlockedAt: { type: ScalarTypeEnum.DateTime(), isOptional: true }
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
var EnrollInCourseInput = defineSchemaModel({
|
|
101
|
-
name: "EnrollInCourseInput",
|
|
102
|
-
description: "Input for enrolling in a course",
|
|
103
|
-
fields: {
|
|
104
|
-
courseId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
var CompleteLessonInput = defineSchemaModel({
|
|
108
|
-
name: "CompleteLessonInput",
|
|
109
|
-
description: "Input for completing a lesson",
|
|
110
|
-
fields: {
|
|
111
|
-
lessonId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
112
|
-
score: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
|
|
113
|
-
timeSpent: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
var SubmitCardReviewInput = defineSchemaModel({
|
|
117
|
-
name: "SubmitCardReviewInput",
|
|
118
|
-
description: "Input for submitting a card review",
|
|
119
|
-
fields: {
|
|
120
|
-
cardId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
121
|
-
rating: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
122
|
-
responseTimeMs: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
var GetDueCardsInput = defineSchemaModel({
|
|
126
|
-
name: "GetDueCardsInput",
|
|
127
|
-
description: "Input for getting due cards",
|
|
128
|
-
fields: {
|
|
129
|
-
deckId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
130
|
-
limit: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
var GetDueCardsOutput = defineSchemaModel({
|
|
134
|
-
name: "GetDueCardsOutput",
|
|
135
|
-
description: "Output for getting due cards",
|
|
136
|
-
fields: {
|
|
137
|
-
cards: { type: CardModel, isArray: true, isOptional: false },
|
|
138
|
-
total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
var GetLearnerDashboardInput = defineSchemaModel({
|
|
142
|
-
name: "GetLearnerDashboardInput",
|
|
143
|
-
description: "Input for getting learner dashboard",
|
|
144
|
-
fields: {
|
|
145
|
-
learnerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true }
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
var LearnerDashboardModel = defineSchemaModel({
|
|
149
|
-
name: "LearnerDashboard",
|
|
150
|
-
description: "Learner dashboard data",
|
|
151
|
-
fields: {
|
|
152
|
-
learner: { type: LearnerModel, isOptional: false },
|
|
153
|
-
currentStreak: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
154
|
-
dailyXpGoal: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
155
|
-
dailyXpProgress: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
156
|
-
activeEnrollments: {
|
|
157
|
-
type: EnrollmentModel,
|
|
158
|
-
isArray: true,
|
|
159
|
-
isOptional: false
|
|
160
|
-
},
|
|
161
|
-
recentAchievements: {
|
|
162
|
-
type: AchievementModel,
|
|
163
|
-
isArray: true,
|
|
164
|
-
isOptional: false
|
|
165
|
-
},
|
|
166
|
-
dueCardCount: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
var SuccessOutput = defineSchemaModel({
|
|
170
|
-
name: "SuccessOutput",
|
|
171
|
-
description: "Generic success output",
|
|
172
|
-
fields: {
|
|
173
|
-
success: { type: ScalarTypeEnum.Boolean(), isOptional: false },
|
|
174
|
-
xpEarned: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// src/contracts/shared.ts
|
|
179
|
-
var LEARNING_JOURNEY_OWNERS = ["modules.learning-journey"];
|
|
180
|
-
|
|
181
|
-
// src/contracts/onboarding.ts
|
|
182
|
-
import { defineCommand, defineQuery } from "@contractspec/lib.contracts-spec";
|
|
183
|
-
import { defineSchemaModel as defineSchemaModel2, ScalarTypeEnum as ScalarTypeEnum2 } from "@contractspec/lib.schema";
|
|
184
|
-
var OnboardingStepConditionModel = defineSchemaModel2({
|
|
185
|
-
name: "OnboardingStepCondition",
|
|
186
|
-
description: "Structured completion condition for onboarding steps.",
|
|
187
|
-
fields: {
|
|
188
|
-
eventName: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
189
|
-
eventVersion: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
190
|
-
sourceModule: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
191
|
-
payloadFilter: { type: ScalarTypeEnum2.JSON(), isOptional: true }
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
var OnboardingStepModel = defineSchemaModel2({
|
|
195
|
-
name: "OnboardingStep",
|
|
196
|
-
description: "Declarative onboarding step definition.",
|
|
197
|
-
fields: {
|
|
198
|
-
id: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
199
|
-
trackId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
200
|
-
title: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
201
|
-
description: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
202
|
-
instructions: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
203
|
-
helpUrl: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
204
|
-
order: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
|
|
205
|
-
completionEvent: {
|
|
206
|
-
type: ScalarTypeEnum2.String_unsecure(),
|
|
207
|
-
isOptional: false
|
|
208
|
-
},
|
|
209
|
-
completionCondition: {
|
|
210
|
-
type: OnboardingStepConditionModel,
|
|
211
|
-
isOptional: true
|
|
212
|
-
},
|
|
213
|
-
xpReward: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: true },
|
|
214
|
-
isRequired: { type: ScalarTypeEnum2.Boolean(), isOptional: true },
|
|
215
|
-
canSkip: { type: ScalarTypeEnum2.Boolean(), isOptional: true },
|
|
216
|
-
actionUrl: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
217
|
-
actionLabel: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
218
|
-
metadata: { type: ScalarTypeEnum2.JSON(), isOptional: true }
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
var OnboardingTrackModel = defineSchemaModel2({
|
|
222
|
-
name: "OnboardingTrack",
|
|
223
|
-
description: "Onboarding track metadata and steps.",
|
|
224
|
-
fields: {
|
|
225
|
-
id: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
226
|
-
productId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
227
|
-
name: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
228
|
-
description: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
229
|
-
targetUserSegment: {
|
|
230
|
-
type: ScalarTypeEnum2.String_unsecure(),
|
|
231
|
-
isOptional: true
|
|
232
|
-
},
|
|
233
|
-
targetRole: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
234
|
-
isActive: { type: ScalarTypeEnum2.Boolean(), isOptional: true },
|
|
235
|
-
isRequired: { type: ScalarTypeEnum2.Boolean(), isOptional: true },
|
|
236
|
-
canSkip: { type: ScalarTypeEnum2.Boolean(), isOptional: true },
|
|
237
|
-
totalXp: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: true },
|
|
238
|
-
completionXpBonus: {
|
|
239
|
-
type: ScalarTypeEnum2.Int_unsecure(),
|
|
240
|
-
isOptional: true
|
|
241
|
-
},
|
|
242
|
-
completionBadgeKey: {
|
|
243
|
-
type: ScalarTypeEnum2.String_unsecure(),
|
|
244
|
-
isOptional: true
|
|
245
|
-
},
|
|
246
|
-
streakHoursWindow: {
|
|
247
|
-
type: ScalarTypeEnum2.Int_unsecure(),
|
|
248
|
-
isOptional: true
|
|
249
|
-
},
|
|
250
|
-
streakBonusXp: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: true },
|
|
251
|
-
metadata: { type: ScalarTypeEnum2.JSON(), isOptional: true },
|
|
252
|
-
steps: { type: OnboardingStepModel, isArray: true, isOptional: false }
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
var OnboardingStepProgressModel = defineSchemaModel2({
|
|
256
|
-
name: "OnboardingStepProgress",
|
|
257
|
-
description: "Progress for a specific onboarding step.",
|
|
258
|
-
fields: {
|
|
259
|
-
stepId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
260
|
-
status: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
261
|
-
xpEarned: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: true },
|
|
262
|
-
triggeringEvent: {
|
|
263
|
-
type: ScalarTypeEnum2.String_unsecure(),
|
|
264
|
-
isOptional: true
|
|
265
|
-
},
|
|
266
|
-
eventPayload: { type: ScalarTypeEnum2.JSON(), isOptional: true },
|
|
267
|
-
completedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: true }
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
var OnboardingProgressModel = defineSchemaModel2({
|
|
271
|
-
name: "OnboardingProgress",
|
|
272
|
-
description: "Aggregated progress for an onboarding track.",
|
|
273
|
-
fields: {
|
|
274
|
-
learnerId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
275
|
-
trackId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
276
|
-
progress: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
|
|
277
|
-
isCompleted: { type: ScalarTypeEnum2.Boolean(), isOptional: false },
|
|
278
|
-
xpEarned: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: true },
|
|
279
|
-
startedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: true },
|
|
280
|
-
completedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: true },
|
|
281
|
-
lastActivityAt: { type: ScalarTypeEnum2.DateTime(), isOptional: true },
|
|
282
|
-
steps: {
|
|
283
|
-
type: OnboardingStepProgressModel,
|
|
284
|
-
isArray: true,
|
|
285
|
-
isOptional: true
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
var ListOnboardingTracksInput = defineSchemaModel2({
|
|
290
|
-
name: "ListOnboardingTracksInput",
|
|
291
|
-
description: "Filters for listing onboarding tracks.",
|
|
292
|
-
fields: {
|
|
293
|
-
learnerId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
294
|
-
productId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
295
|
-
trackIds: {
|
|
296
|
-
type: ScalarTypeEnum2.String_unsecure(),
|
|
297
|
-
isArray: true,
|
|
298
|
-
isOptional: true
|
|
299
|
-
},
|
|
300
|
-
includeProgress: {
|
|
301
|
-
type: ScalarTypeEnum2.Boolean(),
|
|
302
|
-
isOptional: true
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
var ListOnboardingTracksOutput = defineSchemaModel2({
|
|
307
|
-
name: "ListOnboardingTracksOutput",
|
|
308
|
-
description: "Available onboarding tracks with optional progress.",
|
|
309
|
-
fields: {
|
|
310
|
-
tracks: { type: OnboardingTrackModel, isArray: true, isOptional: false },
|
|
311
|
-
progress: {
|
|
312
|
-
type: OnboardingProgressModel,
|
|
313
|
-
isArray: true,
|
|
314
|
-
isOptional: true
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
var GetOnboardingProgressInput = defineSchemaModel2({
|
|
319
|
-
name: "GetOnboardingProgressInput",
|
|
320
|
-
description: "Input for fetching onboarding progress for a track.",
|
|
321
|
-
fields: {
|
|
322
|
-
trackId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
323
|
-
learnerId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true }
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
var RecordOnboardingEventInput = defineSchemaModel2({
|
|
327
|
-
name: "RecordOnboardingEventInput",
|
|
328
|
-
description: "Record a domain event to advance onboarding progress via completion conditions.",
|
|
329
|
-
fields: {
|
|
330
|
-
learnerId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
331
|
-
trackId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
332
|
-
eventName: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
333
|
-
eventVersion: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
334
|
-
eventPayload: { type: ScalarTypeEnum2.JSON(), isOptional: true },
|
|
335
|
-
occurredAt: { type: ScalarTypeEnum2.DateTime(), isOptional: true }
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
var ListOnboardingTracksContract = defineQuery({
|
|
339
|
-
meta: {
|
|
340
|
-
key: "learning.onboarding.listTracks",
|
|
341
|
-
version: "1.0.0",
|
|
342
|
-
stability: "stable",
|
|
343
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
344
|
-
tags: ["learning", "onboarding", "journey"],
|
|
345
|
-
description: "List onboarding tracks available to a learner or product.",
|
|
346
|
-
goal: "Expose track catalog for UI/API surfaces.",
|
|
347
|
-
context: "Called when showing onboarding/learning journey catalog."
|
|
348
|
-
},
|
|
349
|
-
io: {
|
|
350
|
-
input: ListOnboardingTracksInput,
|
|
351
|
-
output: ListOnboardingTracksOutput
|
|
352
|
-
},
|
|
353
|
-
policy: {
|
|
354
|
-
auth: "user"
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
var GetOnboardingProgressContract = defineQuery({
|
|
358
|
-
meta: {
|
|
359
|
-
key: "learning.onboarding.getProgress",
|
|
360
|
-
version: "1.0.0",
|
|
361
|
-
stability: "stable",
|
|
362
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
363
|
-
tags: ["learning", "onboarding", "journey"],
|
|
364
|
-
description: "Fetch onboarding progress for a specific track.",
|
|
365
|
-
goal: "Display learner progress and remaining steps.",
|
|
366
|
-
context: "Called when rendering a track detail or widget."
|
|
367
|
-
},
|
|
368
|
-
io: {
|
|
369
|
-
input: GetOnboardingProgressInput,
|
|
370
|
-
output: OnboardingProgressModel
|
|
371
|
-
},
|
|
372
|
-
policy: {
|
|
373
|
-
auth: "user"
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
var RecordOnboardingEventContract = defineCommand({
|
|
377
|
-
meta: {
|
|
378
|
-
key: "learning.onboarding.recordEvent",
|
|
379
|
-
version: "1.0.0",
|
|
380
|
-
stability: "stable",
|
|
381
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
382
|
-
tags: ["learning", "onboarding", "events"],
|
|
383
|
-
description: "Record a domain event to evaluate onboarding step completion conditions.",
|
|
384
|
-
goal: "Advance onboarding automatically from product events.",
|
|
385
|
-
context: "Called by event bus handlers when relevant product events fire (e.g., deal.created)."
|
|
386
|
-
},
|
|
387
|
-
io: {
|
|
388
|
-
input: RecordOnboardingEventInput,
|
|
389
|
-
output: SuccessOutput,
|
|
390
|
-
errors: {
|
|
391
|
-
TRACK_NOT_FOUND: {
|
|
392
|
-
description: "Track not found for event routing",
|
|
393
|
-
http: 404,
|
|
394
|
-
gqlCode: "TRACK_NOT_FOUND",
|
|
395
|
-
when: "Track ID or routing context is invalid"
|
|
396
|
-
},
|
|
397
|
-
STEP_NOT_FOUND: {
|
|
398
|
-
description: "Step not found for completion condition",
|
|
399
|
-
http: 404,
|
|
400
|
-
gqlCode: "STEP_NOT_FOUND",
|
|
401
|
-
when: "No step matches the incoming event"
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
},
|
|
405
|
-
policy: {
|
|
406
|
-
auth: "user"
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
// src/contracts/operations.ts
|
|
411
|
-
import { defineCommand as defineCommand2, defineQuery as defineQuery2 } from "@contractspec/lib.contracts-spec";
|
|
412
|
-
var EnrollInCourseContract = defineCommand2({
|
|
413
|
-
meta: {
|
|
414
|
-
key: "learning.enroll",
|
|
415
|
-
version: "1.0.0",
|
|
416
|
-
stability: "stable",
|
|
417
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
418
|
-
tags: ["learning", "enrollment"],
|
|
419
|
-
description: "Enroll in a course.",
|
|
420
|
-
goal: "Start learning a new course.",
|
|
421
|
-
context: "Called when a learner wants to start a course."
|
|
422
|
-
},
|
|
423
|
-
io: {
|
|
424
|
-
input: EnrollInCourseInput,
|
|
425
|
-
output: EnrollmentModel,
|
|
426
|
-
errors: {
|
|
427
|
-
COURSE_NOT_FOUND: {
|
|
428
|
-
description: "Course does not exist",
|
|
429
|
-
http: 404,
|
|
430
|
-
gqlCode: "COURSE_NOT_FOUND",
|
|
431
|
-
when: "Course ID is invalid"
|
|
432
|
-
},
|
|
433
|
-
ALREADY_ENROLLED: {
|
|
434
|
-
description: "Already enrolled in course",
|
|
435
|
-
http: 409,
|
|
436
|
-
gqlCode: "ALREADY_ENROLLED",
|
|
437
|
-
when: "Learner is already enrolled"
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
},
|
|
441
|
-
policy: {
|
|
442
|
-
auth: "user"
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
var CompleteLessonContract = defineCommand2({
|
|
446
|
-
meta: {
|
|
447
|
-
key: "learning.completeLesson",
|
|
448
|
-
version: "1.0.0",
|
|
449
|
-
stability: "stable",
|
|
450
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
451
|
-
tags: ["learning", "progress"],
|
|
452
|
-
description: "Mark a lesson as completed.",
|
|
453
|
-
goal: "Record lesson completion and earn XP.",
|
|
454
|
-
context: "Called when a learner finishes a lesson."
|
|
455
|
-
},
|
|
456
|
-
io: {
|
|
457
|
-
input: CompleteLessonInput,
|
|
458
|
-
output: SuccessOutput,
|
|
459
|
-
errors: {
|
|
460
|
-
LESSON_NOT_FOUND: {
|
|
461
|
-
description: "Lesson does not exist",
|
|
462
|
-
http: 404,
|
|
463
|
-
gqlCode: "LESSON_NOT_FOUND",
|
|
464
|
-
when: "Lesson ID is invalid"
|
|
465
|
-
},
|
|
466
|
-
NOT_ENROLLED: {
|
|
467
|
-
description: "Not enrolled in course",
|
|
468
|
-
http: 403,
|
|
469
|
-
gqlCode: "NOT_ENROLLED",
|
|
470
|
-
when: "Learner is not enrolled in the course"
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
},
|
|
474
|
-
policy: {
|
|
475
|
-
auth: "user"
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
var SubmitCardReviewContract = defineCommand2({
|
|
479
|
-
meta: {
|
|
480
|
-
key: "learning.submitCardReview",
|
|
481
|
-
version: "1.0.0",
|
|
482
|
-
stability: "stable",
|
|
483
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
484
|
-
tags: ["learning", "flashcards"],
|
|
485
|
-
description: "Submit a flashcard review.",
|
|
486
|
-
goal: "Record review and update SRS schedule.",
|
|
487
|
-
context: "Called when reviewing flashcards."
|
|
488
|
-
},
|
|
489
|
-
io: {
|
|
490
|
-
input: SubmitCardReviewInput,
|
|
491
|
-
output: SuccessOutput,
|
|
492
|
-
errors: {
|
|
493
|
-
CARD_NOT_FOUND: {
|
|
494
|
-
description: "Card does not exist",
|
|
495
|
-
http: 404,
|
|
496
|
-
gqlCode: "CARD_NOT_FOUND",
|
|
497
|
-
when: "Card ID is invalid"
|
|
498
|
-
},
|
|
499
|
-
INVALID_RATING: {
|
|
500
|
-
description: "Invalid rating",
|
|
501
|
-
http: 400,
|
|
502
|
-
gqlCode: "INVALID_RATING",
|
|
503
|
-
when: "Rating must be AGAIN, HARD, GOOD, or EASY"
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
},
|
|
507
|
-
policy: {
|
|
508
|
-
auth: "user"
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
var GetDueCardsContract = defineQuery2({
|
|
512
|
-
meta: {
|
|
513
|
-
key: "learning.getDueCards",
|
|
514
|
-
version: "1.0.0",
|
|
515
|
-
stability: "stable",
|
|
516
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
517
|
-
tags: ["learning", "flashcards"],
|
|
518
|
-
description: "Get flashcards due for review.",
|
|
519
|
-
goal: "Get the next batch of cards to review.",
|
|
520
|
-
context: "Called when starting a review session."
|
|
521
|
-
},
|
|
522
|
-
io: {
|
|
523
|
-
input: GetDueCardsInput,
|
|
524
|
-
output: GetDueCardsOutput
|
|
525
|
-
},
|
|
526
|
-
policy: {
|
|
527
|
-
auth: "user"
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
var GetLearnerDashboardContract = defineQuery2({
|
|
531
|
-
meta: {
|
|
532
|
-
key: "learning.getDashboard",
|
|
533
|
-
version: "1.0.0",
|
|
534
|
-
stability: "stable",
|
|
535
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
536
|
-
tags: ["learning", "dashboard"],
|
|
537
|
-
description: "Get learner dashboard data.",
|
|
538
|
-
goal: "Display learner progress and stats.",
|
|
539
|
-
context: "Called when viewing the learning dashboard."
|
|
540
|
-
},
|
|
541
|
-
io: {
|
|
542
|
-
input: GetLearnerDashboardInput,
|
|
543
|
-
output: LearnerDashboardModel
|
|
544
|
-
},
|
|
545
|
-
policy: {
|
|
546
|
-
auth: "user"
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
// src/docs/learning-journey.docblock.ts
|
|
550
|
-
import { registerDocBlocks } from "@contractspec/lib.contracts-spec/docs";
|
|
551
|
-
var learningJourneyDocBlocks = [
|
|
552
|
-
{
|
|
553
|
-
id: "docs.learning-journey.engine",
|
|
554
|
-
title: "Learning Journey Engine",
|
|
555
|
-
summary: "Tracks learners, tracks/modules/steps, progress, quizzes, streaks, XP, and AI coaching hooks for product-integrated onboarding.",
|
|
556
|
-
kind: "reference",
|
|
557
|
-
visibility: "public",
|
|
558
|
-
route: "/docs/learning-journey/engine",
|
|
559
|
-
tags: ["learning", "onboarding", "journey", "education"],
|
|
560
|
-
body: `## Capabilities
|
|
1
|
+
import{defineSchemaModel as M,ScalarTypeEnum as O}from"@contractspec/lib.schema";var C0=M({name:"Course",description:"A learning course",fields:{id:{type:O.String_unsecure(),isOptional:!1},title:{type:O.String_unsecure(),isOptional:!1},slug:{type:O.String_unsecure(),isOptional:!1},description:{type:O.String_unsecure(),isOptional:!0},difficulty:{type:O.String_unsecure(),isOptional:!1},status:{type:O.String_unsecure(),isOptional:!1},estimatedDuration:{type:O.Int_unsecure(),isOptional:!0},thumbnailUrl:{type:O.String_unsecure(),isOptional:!0},createdAt:{type:O.DateTime(),isOptional:!1}}}),yj=M({name:"Learner",description:"A learner profile",fields:{id:{type:O.String_unsecure(),isOptional:!1},userId:{type:O.String_unsecure(),isOptional:!1},displayName:{type:O.String_unsecure(),isOptional:!0},level:{type:O.Int_unsecure(),isOptional:!1},totalXp:{type:O.Int_unsecure(),isOptional:!1},currentStreak:{type:O.Int_unsecure(),isOptional:!1},longestStreak:{type:O.Int_unsecure(),isOptional:!1},createdAt:{type:O.DateTime(),isOptional:!1}}}),S=M({name:"Enrollment",description:"A course enrollment",fields:{id:{type:O.String_unsecure(),isOptional:!1},learnerId:{type:O.String_unsecure(),isOptional:!1},courseId:{type:O.String_unsecure(),isOptional:!1},status:{type:O.String_unsecure(),isOptional:!1},progress:{type:O.Int_unsecure(),isOptional:!1},startedAt:{type:O.DateTime(),isOptional:!0},completedAt:{type:O.DateTime(),isOptional:!0}}}),L0=M({name:"LessonProgress",description:"Lesson progress",fields:{id:{type:O.String_unsecure(),isOptional:!1},learnerId:{type:O.String_unsecure(),isOptional:!1},lessonId:{type:O.String_unsecure(),isOptional:!1},status:{type:O.String_unsecure(),isOptional:!1},progress:{type:O.Int_unsecure(),isOptional:!1},score:{type:O.Int_unsecure(),isOptional:!0},timeSpent:{type:O.Int_unsecure(),isOptional:!1},completedAt:{type:O.DateTime(),isOptional:!0}}}),h0=M({name:"Deck",description:"A flashcard deck",fields:{id:{type:O.String_unsecure(),isOptional:!1},title:{type:O.String_unsecure(),isOptional:!1},description:{type:O.String_unsecure(),isOptional:!0},cardCount:{type:O.Int_unsecure(),isOptional:!1},isPublic:{type:O.Boolean(),isOptional:!1},createdAt:{type:O.DateTime(),isOptional:!1}}}),Sj=M({name:"Card",description:"A flashcard",fields:{id:{type:O.String_unsecure(),isOptional:!1},deckId:{type:O.String_unsecure(),isOptional:!1},front:{type:O.String_unsecure(),isOptional:!1},back:{type:O.String_unsecure(),isOptional:!1},hints:{type:O.JSON(),isOptional:!0},isDue:{type:O.Boolean(),isOptional:!1},nextReviewAt:{type:O.DateTime(),isOptional:!0}}}),rj=M({name:"Achievement",description:"An achievement",fields:{id:{type:O.String_unsecure(),isOptional:!1},key:{type:O.String_unsecure(),isOptional:!1},name:{type:O.String_unsecure(),isOptional:!1},description:{type:O.String_unsecure(),isOptional:!1},icon:{type:O.String_unsecure(),isOptional:!0},xpReward:{type:O.Int_unsecure(),isOptional:!1},unlockedAt:{type:O.DateTime(),isOptional:!0}}}),E=M({name:"EnrollInCourseInput",description:"Input for enrolling in a course",fields:{courseId:{type:O.String_unsecure(),isOptional:!1}}}),t=M({name:"CompleteLessonInput",description:"Input for completing a lesson",fields:{lessonId:{type:O.String_unsecure(),isOptional:!1},score:{type:O.Int_unsecure(),isOptional:!0},timeSpent:{type:O.Int_unsecure(),isOptional:!1}}}),f=M({name:"SubmitCardReviewInput",description:"Input for submitting a card review",fields:{cardId:{type:O.String_unsecure(),isOptional:!1},rating:{type:O.String_unsecure(),isOptional:!1},responseTimeMs:{type:O.Int_unsecure(),isOptional:!0}}}),a=M({name:"GetDueCardsInput",description:"Input for getting due cards",fields:{deckId:{type:O.String_unsecure(),isOptional:!0},limit:{type:O.Int_unsecure(),isOptional:!0}}}),n=M({name:"GetDueCardsOutput",description:"Output for getting due cards",fields:{cards:{type:Sj,isArray:!0,isOptional:!1},total:{type:O.Int_unsecure(),isOptional:!1}}}),l=M({name:"GetLearnerDashboardInput",description:"Input for getting learner dashboard",fields:{learnerId:{type:O.String_unsecure(),isOptional:!0}}}),d=M({name:"LearnerDashboard",description:"Learner dashboard data",fields:{learner:{type:yj,isOptional:!1},currentStreak:{type:O.Int_unsecure(),isOptional:!1},dailyXpGoal:{type:O.Int_unsecure(),isOptional:!1},dailyXpProgress:{type:O.Int_unsecure(),isOptional:!1},activeEnrollments:{type:S,isArray:!0,isOptional:!1},recentAchievements:{type:rj,isArray:!0,isOptional:!1},dueCardCount:{type:O.Int_unsecure(),isOptional:!1}}}),N=M({name:"SuccessOutput",description:"Generic success output",fields:{success:{type:O.Boolean(),isOptional:!1},xpEarned:{type:O.Int_unsecure(),isOptional:!0}}});var J=["modules.learning-journey"];import{defineCommand as cj,defineQuery as i}from"@contractspec/lib.contracts-spec";import{defineSchemaModel as g,ScalarTypeEnum as D}from"@contractspec/lib.schema";var mj=g({name:"OnboardingStepCondition",description:"Structured completion condition for onboarding steps.",fields:{eventName:{type:D.String_unsecure(),isOptional:!1},eventVersion:{type:D.String_unsecure(),isOptional:!0},sourceModule:{type:D.String_unsecure(),isOptional:!0},payloadFilter:{type:D.JSON(),isOptional:!0}}}),Ej=g({name:"OnboardingStep",description:"Declarative onboarding step definition.",fields:{id:{type:D.String_unsecure(),isOptional:!1},trackId:{type:D.String_unsecure(),isOptional:!1},title:{type:D.String_unsecure(),isOptional:!1},description:{type:D.String_unsecure(),isOptional:!0},instructions:{type:D.String_unsecure(),isOptional:!0},helpUrl:{type:D.String_unsecure(),isOptional:!0},order:{type:D.Int_unsecure(),isOptional:!1},completionEvent:{type:D.String_unsecure(),isOptional:!1},completionCondition:{type:mj,isOptional:!0},xpReward:{type:D.Int_unsecure(),isOptional:!0},isRequired:{type:D.Boolean(),isOptional:!0},canSkip:{type:D.Boolean(),isOptional:!0},actionUrl:{type:D.String_unsecure(),isOptional:!0},actionLabel:{type:D.String_unsecure(),isOptional:!0},metadata:{type:D.JSON(),isOptional:!0}}}),tj=g({name:"OnboardingTrack",description:"Onboarding track metadata and steps.",fields:{id:{type:D.String_unsecure(),isOptional:!1},productId:{type:D.String_unsecure(),isOptional:!0},name:{type:D.String_unsecure(),isOptional:!1},description:{type:D.String_unsecure(),isOptional:!0},targetUserSegment:{type:D.String_unsecure(),isOptional:!0},targetRole:{type:D.String_unsecure(),isOptional:!0},isActive:{type:D.Boolean(),isOptional:!0},isRequired:{type:D.Boolean(),isOptional:!0},canSkip:{type:D.Boolean(),isOptional:!0},totalXp:{type:D.Int_unsecure(),isOptional:!0},completionXpBonus:{type:D.Int_unsecure(),isOptional:!0},completionBadgeKey:{type:D.String_unsecure(),isOptional:!0},streakHoursWindow:{type:D.Int_unsecure(),isOptional:!0},streakBonusXp:{type:D.Int_unsecure(),isOptional:!0},metadata:{type:D.JSON(),isOptional:!0},steps:{type:Ej,isArray:!0,isOptional:!1}}}),fj=g({name:"OnboardingStepProgress",description:"Progress for a specific onboarding step.",fields:{stepId:{type:D.String_unsecure(),isOptional:!1},status:{type:D.String_unsecure(),isOptional:!1},xpEarned:{type:D.Int_unsecure(),isOptional:!0},triggeringEvent:{type:D.String_unsecure(),isOptional:!0},eventPayload:{type:D.JSON(),isOptional:!0},completedAt:{type:D.DateTime(),isOptional:!0}}}),e=g({name:"OnboardingProgress",description:"Aggregated progress for an onboarding track.",fields:{learnerId:{type:D.String_unsecure(),isOptional:!0},trackId:{type:D.String_unsecure(),isOptional:!1},progress:{type:D.Int_unsecure(),isOptional:!1},isCompleted:{type:D.Boolean(),isOptional:!1},xpEarned:{type:D.Int_unsecure(),isOptional:!0},startedAt:{type:D.DateTime(),isOptional:!0},completedAt:{type:D.DateTime(),isOptional:!0},lastActivityAt:{type:D.DateTime(),isOptional:!0},steps:{type:fj,isArray:!0,isOptional:!0}}}),aj=g({name:"ListOnboardingTracksInput",description:"Filters for listing onboarding tracks.",fields:{learnerId:{type:D.String_unsecure(),isOptional:!0},productId:{type:D.String_unsecure(),isOptional:!0},trackIds:{type:D.String_unsecure(),isArray:!0,isOptional:!0},includeProgress:{type:D.Boolean(),isOptional:!0}}}),nj=g({name:"ListOnboardingTracksOutput",description:"Available onboarding tracks with optional progress.",fields:{tracks:{type:tj,isArray:!0,isOptional:!1},progress:{type:e,isArray:!0,isOptional:!0}}}),lj=g({name:"GetOnboardingProgressInput",description:"Input for fetching onboarding progress for a track.",fields:{trackId:{type:D.String_unsecure(),isOptional:!1},learnerId:{type:D.String_unsecure(),isOptional:!0}}}),dj=g({name:"RecordOnboardingEventInput",description:"Record a domain event to advance onboarding progress via completion conditions.",fields:{learnerId:{type:D.String_unsecure(),isOptional:!1},trackId:{type:D.String_unsecure(),isOptional:!0},eventName:{type:D.String_unsecure(),isOptional:!1},eventVersion:{type:D.String_unsecure(),isOptional:!0},eventPayload:{type:D.JSON(),isOptional:!0},occurredAt:{type:D.DateTime(),isOptional:!0}}}),R0=i({meta:{key:"learning.onboarding.listTracks",version:"1.0.0",stability:"stable",owners:[...J],tags:["learning","onboarding","journey"],description:"List onboarding tracks available to a learner or product.",goal:"Expose track catalog for UI/API surfaces.",context:"Called when showing onboarding/learning journey catalog."},io:{input:aj,output:nj},policy:{auth:"user"}}),w0=i({meta:{key:"learning.onboarding.getProgress",version:"1.0.0",stability:"stable",owners:[...J],tags:["learning","onboarding","journey"],description:"Fetch onboarding progress for a specific track.",goal:"Display learner progress and remaining steps.",context:"Called when rendering a track detail or widget."},io:{input:lj,output:e},policy:{auth:"user"}}),s0=cj({meta:{key:"learning.onboarding.recordEvent",version:"1.0.0",stability:"stable",owners:[...J],tags:["learning","onboarding","events"],description:"Record a domain event to evaluate onboarding step completion conditions.",goal:"Advance onboarding automatically from product events.",context:"Called by event bus handlers when relevant product events fire (e.g., deal.created)."},io:{input:dj,output:N,errors:{TRACK_NOT_FOUND:{description:"Track not found for event routing",http:404,gqlCode:"TRACK_NOT_FOUND",when:"Track ID or routing context is invalid"},STEP_NOT_FOUND:{description:"Step not found for completion condition",http:404,gqlCode:"STEP_NOT_FOUND",when:"No step matches the incoming event"}}},policy:{auth:"user"}});import{defineCommand as r,defineQuery as jj}from"@contractspec/lib.contracts-spec";var S0=r({meta:{key:"learning.enroll",version:"1.0.0",stability:"stable",owners:[...J],tags:["learning","enrollment"],description:"Enroll in a course.",goal:"Start learning a new course.",context:"Called when a learner wants to start a course."},io:{input:E,output:S,errors:{COURSE_NOT_FOUND:{description:"Course does not exist",http:404,gqlCode:"COURSE_NOT_FOUND",when:"Course ID is invalid"},ALREADY_ENROLLED:{description:"Already enrolled in course",http:409,gqlCode:"ALREADY_ENROLLED",when:"Learner is already enrolled"}}},policy:{auth:"user"}}),r0=r({meta:{key:"learning.completeLesson",version:"1.0.0",stability:"stable",owners:[...J],tags:["learning","progress"],description:"Mark a lesson as completed.",goal:"Record lesson completion and earn XP.",context:"Called when a learner finishes a lesson."},io:{input:t,output:N,errors:{LESSON_NOT_FOUND:{description:"Lesson does not exist",http:404,gqlCode:"LESSON_NOT_FOUND",when:"Lesson ID is invalid"},NOT_ENROLLED:{description:"Not enrolled in course",http:403,gqlCode:"NOT_ENROLLED",when:"Learner is not enrolled in the course"}}},policy:{auth:"user"}}),c0=r({meta:{key:"learning.submitCardReview",version:"1.0.0",stability:"stable",owners:[...J],tags:["learning","flashcards"],description:"Submit a flashcard review.",goal:"Record review and update SRS schedule.",context:"Called when reviewing flashcards."},io:{input:f,output:N,errors:{CARD_NOT_FOUND:{description:"Card does not exist",http:404,gqlCode:"CARD_NOT_FOUND",when:"Card ID is invalid"},INVALID_RATING:{description:"Invalid rating",http:400,gqlCode:"INVALID_RATING",when:"Rating must be AGAIN, HARD, GOOD, or EASY"}}},policy:{auth:"user"}}),m0=jj({meta:{key:"learning.getDueCards",version:"1.0.0",stability:"stable",owners:[...J],tags:["learning","flashcards"],description:"Get flashcards due for review.",goal:"Get the next batch of cards to review.",context:"Called when starting a review session."},io:{input:a,output:n},policy:{auth:"user"}}),E0=jj({meta:{key:"learning.getDashboard",version:"1.0.0",stability:"stable",owners:[...J],tags:["learning","dashboard"],description:"Get learner dashboard data.",goal:"Display learner progress and stats.",context:"Called when viewing the learning dashboard."},io:{input:l,output:d},policy:{auth:"user"}});import{registerDocBlocks as ij}from"@contractspec/lib.contracts-spec/docs";var ej=[{id:"docs.learning-journey.engine",title:"Learning Journey Engine",summary:"Tracks learners, tracks/modules/steps, progress, quizzes, streaks, XP, and AI coaching hooks for product-integrated onboarding.",kind:"reference",visibility:"public",route:"/docs/learning-journey/engine",tags:["learning","onboarding","journey","education"],body:`## Capabilities
|
|
561
2
|
|
|
562
3
|
- **Entities**: Learner, Track, Module, Step, Progress, Quiz, Flashcard, AI Coach, Gamification (XP, streaks, badges).
|
|
563
4
|
- **Contracts**: enroll/resume/advance steps, complete quizzes, record streaks, assign XP, fetch progress dashboards, onboarding list/progress/recordEvent.
|
|
@@ -587,13 +28,13 @@ var learningJourneyDocBlocks = [
|
|
|
587
28
|
|
|
588
29
|
## Example
|
|
589
30
|
|
|
590
|
-
|
|
31
|
+
\`\`\`ts
|
|
591
32
|
import { learningJourneyEntities } from '@contractspec/module.learning-journey';
|
|
592
33
|
import { StreakEngine } from '@contractspec/module.learning-journey/engines';
|
|
593
34
|
|
|
594
35
|
const streak = new StreakEngine({ graceDays: 1 });
|
|
595
36
|
const updated = streak.compute({ lastActiveAt: new Date(), today: new Date() });
|
|
596
|
-
|
|
37
|
+
\`\`\`,
|
|
597
38
|
|
|
598
39
|
## Guardrails
|
|
599
40
|
|
|
@@ -601,17 +42,7 @@ ${"```"},
|
|
|
601
42
|
- Avoid storing PII in content; keep org/user scoping for multi-tenant isolation.
|
|
602
43
|
- Emit analytics and audit logs for completions; respect \`prefers-reduced-motion\` in UIs consuming these specs.
|
|
603
44
|
- Track completion bonuses: \`completionXpBonus\`, \`completionBadgeKey\`, optional \`streakHoursWindow\` + \`streakBonusXp\`.
|
|
604
|
-
`
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
id: "docs.learning-journey.goal",
|
|
608
|
-
title: "Learning Journey — Goal",
|
|
609
|
-
summary: "Why the learning journey engine exists and the outcomes it targets.",
|
|
610
|
-
kind: "goal",
|
|
611
|
-
visibility: "public",
|
|
612
|
-
route: "/docs/learning-journey/goal",
|
|
613
|
-
tags: ["learning", "goal"],
|
|
614
|
-
body: `## Why it matters
|
|
45
|
+
`},{id:"docs.learning-journey.goal",title:"Learning Journey — Goal",summary:"Why the learning journey engine exists and the outcomes it targets.",kind:"goal",visibility:"public",route:"/docs/learning-journey/goal",tags:["learning","goal"],body:`## Why it matters
|
|
615
46
|
- Provides a regenerable onboarding/education engine tied to product signals.
|
|
616
47
|
- Keeps tracks, steps, quizzes, and gamification consistent across surfaces.
|
|
617
48
|
|
|
@@ -621,17 +52,7 @@ ${"```"},
|
|
|
621
52
|
|
|
622
53
|
## Success criteria
|
|
623
54
|
- Journey changes regenerate UI/API/events without drift.
|
|
624
|
-
- Analytics/audit hooks exist for completions and streaks.`
|
|
625
|
-
},
|
|
626
|
-
{
|
|
627
|
-
id: "docs.learning-journey.usage",
|
|
628
|
-
title: "Learning Journey — Usage",
|
|
629
|
-
summary: "How to compose, bind, and regenerate journeys safely.",
|
|
630
|
-
kind: "usage",
|
|
631
|
-
visibility: "public",
|
|
632
|
-
route: "/docs/learning-journey/usage",
|
|
633
|
-
tags: ["learning", "usage"],
|
|
634
|
-
body: `## Setup
|
|
55
|
+
- Analytics/audit hooks exist for completions and streaks.`},{id:"docs.learning-journey.usage",title:"Learning Journey — Usage",summary:"How to compose, bind, and regenerate journeys safely.",kind:"usage",visibility:"public",route:"/docs/learning-journey/usage",tags:["learning","usage"],body:`## Setup
|
|
635
56
|
1) Include \`learningJourneyEntities\` in schema composition.
|
|
636
57
|
2) Register contracts/events from \`@contractspec/module.learning-journey\`.
|
|
637
58
|
3) Bind steps to real product events (e.g., deal.created, run.completed).
|
|
@@ -644,17 +65,7 @@ ${"```"},
|
|
|
644
65
|
## Guardrails
|
|
645
66
|
- Avoid hardcoded progression; keep engines declarative.
|
|
646
67
|
- Emit analytics/audit for completions; respect user locale/accessibility in presentations.
|
|
647
|
-
- Keep content free of PII; scope learners by org/tenant.`
|
|
648
|
-
},
|
|
649
|
-
{
|
|
650
|
-
id: "docs.learning-journey.constraints",
|
|
651
|
-
title: "Learning Journey — Constraints & Safety",
|
|
652
|
-
summary: "Internal guardrails for progression, telemetry, and regeneration semantics.",
|
|
653
|
-
kind: "reference",
|
|
654
|
-
visibility: "internal",
|
|
655
|
-
route: "/docs/learning-journey/constraints",
|
|
656
|
-
tags: ["learning", "constraints", "internal"],
|
|
657
|
-
body: `## Constraints
|
|
68
|
+
- Keep content free of PII; scope learners by org/tenant.`},{id:"docs.learning-journey.constraints",title:"Learning Journey — Constraints & Safety",summary:"Internal guardrails for progression, telemetry, and regeneration semantics.",kind:"reference",visibility:"internal",route:"/docs/learning-journey/constraints",tags:["learning","constraints","internal"],body:`## Constraints
|
|
658
69
|
- Progression (tracks/modules/steps) and engines (SRS, streaks, XP) must stay declarative in spec.
|
|
659
70
|
- Events to emit: learner.enrolled, step.completed, quiz.scored, streak.reset, xp.awarded.
|
|
660
71
|
- Regeneration should not change scoring/streak rules without explicit spec change.
|
|
@@ -666,3322 +77,4 @@ ${"```"},
|
|
|
666
77
|
## Verification
|
|
667
78
|
- Add fixtures for streak/XP rule changes and quiz scoring.
|
|
668
79
|
- Ensure Notifications/Audit wiring persists for completions; analytics emitted for progress.
|
|
669
|
-
- Use Feature Flags to trial new tracks or reward rules; default safe/off.`
|
|
670
|
-
}
|
|
671
|
-
];
|
|
672
|
-
registerDocBlocks(learningJourneyDocBlocks);
|
|
673
|
-
// src/engines/srs.ts
|
|
674
|
-
var DEFAULT_SRS_CONFIG = {
|
|
675
|
-
learningSteps: [1, 10],
|
|
676
|
-
graduatingInterval: 1,
|
|
677
|
-
easyInterval: 4,
|
|
678
|
-
relearningSteps: [10],
|
|
679
|
-
minEaseFactor: 1.3,
|
|
680
|
-
maxInterval: 365,
|
|
681
|
-
intervalModifier: 1,
|
|
682
|
-
newIntervalModifier: 0.5,
|
|
683
|
-
hardIntervalModifier: 1.2,
|
|
684
|
-
easyBonus: 1.3
|
|
685
|
-
};
|
|
686
|
-
|
|
687
|
-
class SRSEngine {
|
|
688
|
-
config;
|
|
689
|
-
constructor(config = {}) {
|
|
690
|
-
this.config = { ...DEFAULT_SRS_CONFIG, ...config };
|
|
691
|
-
}
|
|
692
|
-
calculateNextReview(state, rating, now = new Date) {
|
|
693
|
-
if (!state.isGraduated && !state.isRelearning) {
|
|
694
|
-
return this.handleLearningCard(state, rating, now);
|
|
695
|
-
}
|
|
696
|
-
if (state.isRelearning) {
|
|
697
|
-
return this.handleRelearningCard(state, rating, now);
|
|
698
|
-
}
|
|
699
|
-
return this.handleReviewCard(state, rating, now);
|
|
700
|
-
}
|
|
701
|
-
getInitialState() {
|
|
702
|
-
return {
|
|
703
|
-
interval: 0,
|
|
704
|
-
easeFactor: 2.5,
|
|
705
|
-
repetitions: 0,
|
|
706
|
-
learningStep: 0,
|
|
707
|
-
isGraduated: false,
|
|
708
|
-
isRelearning: false,
|
|
709
|
-
lapses: 0
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
isDue(nextReviewAt, now = new Date) {
|
|
713
|
-
return nextReviewAt <= now;
|
|
714
|
-
}
|
|
715
|
-
getOverdueDays(nextReviewAt, now = new Date) {
|
|
716
|
-
const diff = now.getTime() - nextReviewAt.getTime();
|
|
717
|
-
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
718
|
-
}
|
|
719
|
-
handleLearningCard(state, rating, now) {
|
|
720
|
-
const steps = this.config.learningSteps;
|
|
721
|
-
let newStep = state.learningStep;
|
|
722
|
-
let isGraduated = false;
|
|
723
|
-
let interval = 0;
|
|
724
|
-
let nextReviewAt;
|
|
725
|
-
switch (rating) {
|
|
726
|
-
case "AGAIN":
|
|
727
|
-
newStep = 0;
|
|
728
|
-
interval = steps[0] ?? 1;
|
|
729
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
730
|
-
break;
|
|
731
|
-
case "HARD":
|
|
732
|
-
interval = steps[newStep] ?? steps[0] ?? 1;
|
|
733
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
734
|
-
break;
|
|
735
|
-
case "GOOD":
|
|
736
|
-
newStep++;
|
|
737
|
-
if (newStep >= steps.length) {
|
|
738
|
-
isGraduated = true;
|
|
739
|
-
interval = this.config.graduatingInterval;
|
|
740
|
-
nextReviewAt = this.addDays(now, interval);
|
|
741
|
-
} else {
|
|
742
|
-
interval = steps[newStep] ?? 10;
|
|
743
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
744
|
-
}
|
|
745
|
-
break;
|
|
746
|
-
case "EASY":
|
|
747
|
-
isGraduated = true;
|
|
748
|
-
interval = this.config.easyInterval;
|
|
749
|
-
nextReviewAt = this.addDays(now, interval);
|
|
750
|
-
break;
|
|
751
|
-
}
|
|
752
|
-
return {
|
|
753
|
-
interval: isGraduated ? interval : 0,
|
|
754
|
-
easeFactor: state.easeFactor,
|
|
755
|
-
repetitions: isGraduated ? 1 : 0,
|
|
756
|
-
nextReviewAt,
|
|
757
|
-
learningStep: newStep,
|
|
758
|
-
isGraduated,
|
|
759
|
-
isRelearning: false,
|
|
760
|
-
lapses: state.lapses
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
handleRelearningCard(state, rating, now) {
|
|
764
|
-
const steps = this.config.relearningSteps;
|
|
765
|
-
let newStep = state.learningStep;
|
|
766
|
-
let isRelearning = true;
|
|
767
|
-
let interval = 0;
|
|
768
|
-
let nextReviewAt;
|
|
769
|
-
switch (rating) {
|
|
770
|
-
case "AGAIN":
|
|
771
|
-
newStep = 0;
|
|
772
|
-
interval = steps[0] ?? 10;
|
|
773
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
774
|
-
break;
|
|
775
|
-
case "HARD":
|
|
776
|
-
interval = steps[newStep] ?? steps[0] ?? 10;
|
|
777
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
778
|
-
break;
|
|
779
|
-
case "GOOD":
|
|
780
|
-
newStep++;
|
|
781
|
-
if (newStep >= steps.length) {
|
|
782
|
-
isRelearning = false;
|
|
783
|
-
interval = Math.max(1, Math.floor(state.interval * this.config.newIntervalModifier));
|
|
784
|
-
nextReviewAt = this.addDays(now, interval);
|
|
785
|
-
} else {
|
|
786
|
-
interval = steps[newStep] ?? 10;
|
|
787
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
788
|
-
}
|
|
789
|
-
break;
|
|
790
|
-
case "EASY":
|
|
791
|
-
isRelearning = false;
|
|
792
|
-
interval = Math.max(1, Math.floor(state.interval * this.config.newIntervalModifier * 1.5));
|
|
793
|
-
nextReviewAt = this.addDays(now, interval);
|
|
794
|
-
break;
|
|
795
|
-
}
|
|
796
|
-
return {
|
|
797
|
-
interval: isRelearning ? state.interval : interval,
|
|
798
|
-
easeFactor: state.easeFactor,
|
|
799
|
-
repetitions: isRelearning ? state.repetitions : state.repetitions + 1,
|
|
800
|
-
nextReviewAt,
|
|
801
|
-
learningStep: newStep,
|
|
802
|
-
isGraduated: true,
|
|
803
|
-
isRelearning,
|
|
804
|
-
lapses: state.lapses
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
handleReviewCard(state, rating, now) {
|
|
808
|
-
let newInterval;
|
|
809
|
-
let newEaseFactor = state.easeFactor;
|
|
810
|
-
let repetitions = state.repetitions;
|
|
811
|
-
let isRelearning = false;
|
|
812
|
-
let learningStep = 0;
|
|
813
|
-
let lapses = state.lapses;
|
|
814
|
-
switch (rating) {
|
|
815
|
-
case "AGAIN":
|
|
816
|
-
lapses++;
|
|
817
|
-
isRelearning = true;
|
|
818
|
-
learningStep = 0;
|
|
819
|
-
newEaseFactor = Math.max(this.config.minEaseFactor, newEaseFactor - 0.2);
|
|
820
|
-
newInterval = state.interval;
|
|
821
|
-
return {
|
|
822
|
-
interval: newInterval,
|
|
823
|
-
easeFactor: newEaseFactor,
|
|
824
|
-
repetitions,
|
|
825
|
-
nextReviewAt: this.addMinutes(now, this.config.relearningSteps[0] ?? 10),
|
|
826
|
-
learningStep,
|
|
827
|
-
isGraduated: true,
|
|
828
|
-
isRelearning: true,
|
|
829
|
-
lapses
|
|
830
|
-
};
|
|
831
|
-
case "HARD":
|
|
832
|
-
newEaseFactor = Math.max(this.config.minEaseFactor, newEaseFactor - 0.15);
|
|
833
|
-
newInterval = Math.max(state.interval + 1, state.interval * this.config.hardIntervalModifier);
|
|
834
|
-
break;
|
|
835
|
-
case "GOOD":
|
|
836
|
-
newInterval = state.interval * newEaseFactor * this.config.intervalModifier;
|
|
837
|
-
repetitions++;
|
|
838
|
-
break;
|
|
839
|
-
case "EASY":
|
|
840
|
-
newEaseFactor = newEaseFactor + 0.15;
|
|
841
|
-
newInterval = state.interval * newEaseFactor * this.config.easyBonus * this.config.intervalModifier;
|
|
842
|
-
repetitions++;
|
|
843
|
-
break;
|
|
844
|
-
}
|
|
845
|
-
newInterval = Math.min(Math.round(newInterval), this.config.maxInterval);
|
|
846
|
-
newInterval = Math.max(1, newInterval);
|
|
847
|
-
return {
|
|
848
|
-
interval: newInterval,
|
|
849
|
-
easeFactor: newEaseFactor,
|
|
850
|
-
repetitions,
|
|
851
|
-
nextReviewAt: this.addDays(now, newInterval),
|
|
852
|
-
learningStep,
|
|
853
|
-
isGraduated: true,
|
|
854
|
-
isRelearning,
|
|
855
|
-
lapses
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
addMinutes(date, minutes) {
|
|
859
|
-
return new Date(date.getTime() + minutes * 60 * 1000);
|
|
860
|
-
}
|
|
861
|
-
addDays(date, days) {
|
|
862
|
-
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
var srsEngine = new SRSEngine;
|
|
866
|
-
|
|
867
|
-
// src/engines/streak.ts
|
|
868
|
-
var DEFAULT_STREAK_CONFIG = {
|
|
869
|
-
timezone: "UTC",
|
|
870
|
-
freezesPerMonth: 2,
|
|
871
|
-
maxFreezes: 5,
|
|
872
|
-
gracePeriodHours: 4
|
|
873
|
-
};
|
|
874
|
-
|
|
875
|
-
class StreakEngine {
|
|
876
|
-
config;
|
|
877
|
-
constructor(config = {}) {
|
|
878
|
-
this.config = { ...DEFAULT_STREAK_CONFIG, ...config };
|
|
879
|
-
}
|
|
880
|
-
update(state, now = new Date) {
|
|
881
|
-
const todayDate = this.getDateString(now);
|
|
882
|
-
const result = {
|
|
883
|
-
state: { ...state },
|
|
884
|
-
streakMaintained: false,
|
|
885
|
-
streakLost: false,
|
|
886
|
-
freezeUsed: false,
|
|
887
|
-
newStreak: false,
|
|
888
|
-
daysMissed: 0
|
|
889
|
-
};
|
|
890
|
-
if (!state.lastActivityDate) {
|
|
891
|
-
result.state.currentStreak = 1;
|
|
892
|
-
result.state.longestStreak = Math.max(1, state.longestStreak);
|
|
893
|
-
result.state.lastActivityAt = now;
|
|
894
|
-
result.state.lastActivityDate = todayDate;
|
|
895
|
-
result.newStreak = true;
|
|
896
|
-
result.streakMaintained = true;
|
|
897
|
-
return result;
|
|
898
|
-
}
|
|
899
|
-
if (state.lastActivityDate === todayDate) {
|
|
900
|
-
result.state.lastActivityAt = now;
|
|
901
|
-
result.streakMaintained = true;
|
|
902
|
-
return result;
|
|
903
|
-
}
|
|
904
|
-
const daysSinceActivity = this.getDaysBetween(state.lastActivityDate, todayDate);
|
|
905
|
-
if (daysSinceActivity === 1) {
|
|
906
|
-
result.state.currentStreak = state.currentStreak + 1;
|
|
907
|
-
result.state.longestStreak = Math.max(result.state.currentStreak, state.longestStreak);
|
|
908
|
-
result.state.lastActivityAt = now;
|
|
909
|
-
result.state.lastActivityDate = todayDate;
|
|
910
|
-
result.streakMaintained = true;
|
|
911
|
-
return result;
|
|
912
|
-
}
|
|
913
|
-
result.daysMissed = daysSinceActivity - 1;
|
|
914
|
-
const freezesNeeded = result.daysMissed;
|
|
915
|
-
if (freezesNeeded <= state.freezesRemaining) {
|
|
916
|
-
result.state.freezesRemaining = state.freezesRemaining - freezesNeeded;
|
|
917
|
-
result.state.freezeUsedAt = now;
|
|
918
|
-
result.state.currentStreak = state.currentStreak + 1;
|
|
919
|
-
result.state.longestStreak = Math.max(result.state.currentStreak, state.longestStreak);
|
|
920
|
-
result.state.lastActivityAt = now;
|
|
921
|
-
result.state.lastActivityDate = todayDate;
|
|
922
|
-
result.freezeUsed = true;
|
|
923
|
-
result.streakMaintained = true;
|
|
924
|
-
return result;
|
|
925
|
-
}
|
|
926
|
-
result.streakLost = true;
|
|
927
|
-
result.state.currentStreak = 1;
|
|
928
|
-
result.state.lastActivityAt = now;
|
|
929
|
-
result.state.lastActivityDate = todayDate;
|
|
930
|
-
result.newStreak = true;
|
|
931
|
-
return result;
|
|
932
|
-
}
|
|
933
|
-
checkStatus(state, now = new Date) {
|
|
934
|
-
if (!state.lastActivityDate) {
|
|
935
|
-
return {
|
|
936
|
-
isActive: false,
|
|
937
|
-
willExpireAt: null,
|
|
938
|
-
canUseFreeze: false,
|
|
939
|
-
daysUntilExpiry: 0
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
const todayDate = this.getDateString(now);
|
|
943
|
-
const daysSinceActivity = this.getDaysBetween(state.lastActivityDate, todayDate);
|
|
944
|
-
if (daysSinceActivity === 0) {
|
|
945
|
-
const tomorrow = this.addDays(now, 1);
|
|
946
|
-
tomorrow.setHours(23, 59, 59, 999);
|
|
947
|
-
return {
|
|
948
|
-
isActive: true,
|
|
949
|
-
willExpireAt: tomorrow,
|
|
950
|
-
canUseFreeze: state.freezesRemaining > 0,
|
|
951
|
-
daysUntilExpiry: 1
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
if (daysSinceActivity === 1) {
|
|
955
|
-
const endOfDay = new Date(now);
|
|
956
|
-
endOfDay.setHours(23 + this.config.gracePeriodHours, 59, 59, 999);
|
|
957
|
-
return {
|
|
958
|
-
isActive: true,
|
|
959
|
-
willExpireAt: endOfDay,
|
|
960
|
-
canUseFreeze: state.freezesRemaining > 0,
|
|
961
|
-
daysUntilExpiry: 0
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
const missedDays = daysSinceActivity - 1;
|
|
965
|
-
return {
|
|
966
|
-
isActive: missedDays <= state.freezesRemaining,
|
|
967
|
-
willExpireAt: null,
|
|
968
|
-
canUseFreeze: missedDays <= state.freezesRemaining,
|
|
969
|
-
daysUntilExpiry: -missedDays
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
useFreeze(state, now = new Date) {
|
|
973
|
-
if (state.freezesRemaining <= 0) {
|
|
974
|
-
return null;
|
|
975
|
-
}
|
|
976
|
-
return {
|
|
977
|
-
...state,
|
|
978
|
-
freezesRemaining: state.freezesRemaining - 1,
|
|
979
|
-
freezeUsedAt: now
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
awardMonthlyFreezes(state) {
|
|
983
|
-
return {
|
|
984
|
-
...state,
|
|
985
|
-
freezesRemaining: Math.min(state.freezesRemaining + this.config.freezesPerMonth, this.config.maxFreezes)
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
getInitialState() {
|
|
989
|
-
return {
|
|
990
|
-
currentStreak: 0,
|
|
991
|
-
longestStreak: 0,
|
|
992
|
-
lastActivityAt: null,
|
|
993
|
-
lastActivityDate: null,
|
|
994
|
-
freezesRemaining: this.config.freezesPerMonth,
|
|
995
|
-
freezeUsedAt: null
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
getMilestones(currentStreak) {
|
|
999
|
-
const milestones = [3, 7, 14, 30, 60, 90, 180, 365, 500, 1000];
|
|
1000
|
-
const achieved = milestones.filter((m) => currentStreak >= m);
|
|
1001
|
-
const next = milestones.find((m) => currentStreak < m) ?? null;
|
|
1002
|
-
return { achieved, next };
|
|
1003
|
-
}
|
|
1004
|
-
getDateString(date) {
|
|
1005
|
-
const year = date.getFullYear();
|
|
1006
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
1007
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
1008
|
-
return `${year}-${month}-${day}`;
|
|
1009
|
-
}
|
|
1010
|
-
getDaysBetween(dateStr1, dateStr2) {
|
|
1011
|
-
const date1 = new Date(dateStr1);
|
|
1012
|
-
const date2 = new Date(dateStr2);
|
|
1013
|
-
const diffTime = date2.getTime() - date1.getTime();
|
|
1014
|
-
return Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
|
1015
|
-
}
|
|
1016
|
-
addDays(date, days) {
|
|
1017
|
-
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
var streakEngine = new StreakEngine;
|
|
1021
|
-
|
|
1022
|
-
// src/i18n/catalogs/en.ts
|
|
1023
|
-
import { defineTranslation } from "@contractspec/lib.contracts-spec/translations";
|
|
1024
|
-
var enMessages = defineTranslation({
|
|
1025
|
-
meta: {
|
|
1026
|
-
key: "learning-journey.messages",
|
|
1027
|
-
version: "1.0.0",
|
|
1028
|
-
domain: "learning-journey",
|
|
1029
|
-
description: "XP source labels for the learning-journey module",
|
|
1030
|
-
owners: ["platform"],
|
|
1031
|
-
stability: "experimental"
|
|
1032
|
-
},
|
|
1033
|
-
locale: "en",
|
|
1034
|
-
fallback: "en",
|
|
1035
|
-
messages: {
|
|
1036
|
-
"xp.source.base": {
|
|
1037
|
-
value: "Base",
|
|
1038
|
-
description: "XP breakdown label for base XP"
|
|
1039
|
-
},
|
|
1040
|
-
"xp.source.scoreBonus": {
|
|
1041
|
-
value: "Score Bonus",
|
|
1042
|
-
description: "XP breakdown label for score-based bonus"
|
|
1043
|
-
},
|
|
1044
|
-
"xp.source.perfectScore": {
|
|
1045
|
-
value: "Perfect Score",
|
|
1046
|
-
description: "XP breakdown label for perfect score bonus"
|
|
1047
|
-
},
|
|
1048
|
-
"xp.source.firstAttempt": {
|
|
1049
|
-
value: "First Attempt",
|
|
1050
|
-
description: "XP breakdown label for first attempt bonus"
|
|
1051
|
-
},
|
|
1052
|
-
"xp.source.retryPenalty": {
|
|
1053
|
-
value: "Retry Penalty",
|
|
1054
|
-
description: "XP breakdown label for retry penalty"
|
|
1055
|
-
},
|
|
1056
|
-
"xp.source.streakBonus": {
|
|
1057
|
-
value: "Streak Bonus",
|
|
1058
|
-
description: "XP breakdown label for streak bonus"
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
|
-
// src/i18n/catalogs/es.ts
|
|
1064
|
-
import { defineTranslation as defineTranslation2 } from "@contractspec/lib.contracts-spec/translations";
|
|
1065
|
-
var esMessages = defineTranslation2({
|
|
1066
|
-
meta: {
|
|
1067
|
-
key: "learning-journey.messages",
|
|
1068
|
-
version: "1.0.0",
|
|
1069
|
-
domain: "learning-journey",
|
|
1070
|
-
description: "XP source labels (Spanish)",
|
|
1071
|
-
owners: ["platform"],
|
|
1072
|
-
stability: "experimental"
|
|
1073
|
-
},
|
|
1074
|
-
locale: "es",
|
|
1075
|
-
fallback: "en",
|
|
1076
|
-
messages: {
|
|
1077
|
-
"xp.source.base": {
|
|
1078
|
-
value: "Base",
|
|
1079
|
-
description: "XP breakdown label for base XP"
|
|
1080
|
-
},
|
|
1081
|
-
"xp.source.scoreBonus": {
|
|
1082
|
-
value: "Bonificación por puntuación",
|
|
1083
|
-
description: "XP breakdown label for score-based bonus"
|
|
1084
|
-
},
|
|
1085
|
-
"xp.source.perfectScore": {
|
|
1086
|
-
value: "Puntuación perfecta",
|
|
1087
|
-
description: "XP breakdown label for perfect score bonus"
|
|
1088
|
-
},
|
|
1089
|
-
"xp.source.firstAttempt": {
|
|
1090
|
-
value: "Primer intento",
|
|
1091
|
-
description: "XP breakdown label for first attempt bonus"
|
|
1092
|
-
},
|
|
1093
|
-
"xp.source.retryPenalty": {
|
|
1094
|
-
value: "Penalización por reintento",
|
|
1095
|
-
description: "XP breakdown label for retry penalty"
|
|
1096
|
-
},
|
|
1097
|
-
"xp.source.streakBonus": {
|
|
1098
|
-
value: "Bonificación por racha",
|
|
1099
|
-
description: "XP breakdown label for streak bonus"
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
// src/i18n/catalogs/fr.ts
|
|
1105
|
-
import { defineTranslation as defineTranslation3 } from "@contractspec/lib.contracts-spec/translations";
|
|
1106
|
-
var frMessages = defineTranslation3({
|
|
1107
|
-
meta: {
|
|
1108
|
-
key: "learning-journey.messages",
|
|
1109
|
-
version: "1.0.0",
|
|
1110
|
-
domain: "learning-journey",
|
|
1111
|
-
description: "XP source labels (French)",
|
|
1112
|
-
owners: ["platform"],
|
|
1113
|
-
stability: "experimental"
|
|
1114
|
-
},
|
|
1115
|
-
locale: "fr",
|
|
1116
|
-
fallback: "en",
|
|
1117
|
-
messages: {
|
|
1118
|
-
"xp.source.base": {
|
|
1119
|
-
value: "Base",
|
|
1120
|
-
description: "XP breakdown label for base XP"
|
|
1121
|
-
},
|
|
1122
|
-
"xp.source.scoreBonus": {
|
|
1123
|
-
value: "Bonus de score",
|
|
1124
|
-
description: "XP breakdown label for score-based bonus"
|
|
1125
|
-
},
|
|
1126
|
-
"xp.source.perfectScore": {
|
|
1127
|
-
value: "Score parfait",
|
|
1128
|
-
description: "XP breakdown label for perfect score bonus"
|
|
1129
|
-
},
|
|
1130
|
-
"xp.source.firstAttempt": {
|
|
1131
|
-
value: "Premier essai",
|
|
1132
|
-
description: "XP breakdown label for first attempt bonus"
|
|
1133
|
-
},
|
|
1134
|
-
"xp.source.retryPenalty": {
|
|
1135
|
-
value: "Pénalité de réessai",
|
|
1136
|
-
description: "XP breakdown label for retry penalty"
|
|
1137
|
-
},
|
|
1138
|
-
"xp.source.streakBonus": {
|
|
1139
|
-
value: "Bonus de série",
|
|
1140
|
-
description: "XP breakdown label for streak bonus"
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
});
|
|
1144
|
-
|
|
1145
|
-
// src/i18n/messages.ts
|
|
1146
|
-
import {
|
|
1147
|
-
createI18nFactory
|
|
1148
|
-
} from "@contractspec/lib.contracts-spec/translations";
|
|
1149
|
-
var factory = createI18nFactory({
|
|
1150
|
-
specKey: "learning-journey.messages",
|
|
1151
|
-
catalogs: [enMessages, frMessages, esMessages]
|
|
1152
|
-
});
|
|
1153
|
-
var createLearningJourneyI18n = factory.create;
|
|
1154
|
-
var getDefaultI18n = factory.getDefault;
|
|
1155
|
-
var resetI18nRegistry = factory.resetRegistry;
|
|
1156
|
-
|
|
1157
|
-
// src/engines/xp.ts
|
|
1158
|
-
var DEFAULT_XP_CONFIG = {
|
|
1159
|
-
baseValues: {
|
|
1160
|
-
lesson_complete: 10,
|
|
1161
|
-
quiz_pass: 20,
|
|
1162
|
-
quiz_perfect: 50,
|
|
1163
|
-
flashcard_review: 1,
|
|
1164
|
-
course_complete: 200,
|
|
1165
|
-
module_complete: 50,
|
|
1166
|
-
streak_bonus: 5,
|
|
1167
|
-
achievement_unlock: 0,
|
|
1168
|
-
daily_goal_complete: 15,
|
|
1169
|
-
first_lesson: 25,
|
|
1170
|
-
onboarding_step: 5,
|
|
1171
|
-
onboarding_complete: 50
|
|
1172
|
-
},
|
|
1173
|
-
scoreThresholds: [
|
|
1174
|
-
{ min: 90, multiplier: 1.5 },
|
|
1175
|
-
{ min: 80, multiplier: 1.25 },
|
|
1176
|
-
{ min: 70, multiplier: 1 },
|
|
1177
|
-
{ min: 60, multiplier: 0.75 },
|
|
1178
|
-
{ min: 0, multiplier: 0.5 }
|
|
1179
|
-
],
|
|
1180
|
-
streakTiers: [
|
|
1181
|
-
{ days: 365, bonus: 50 },
|
|
1182
|
-
{ days: 180, bonus: 30 },
|
|
1183
|
-
{ days: 90, bonus: 20 },
|
|
1184
|
-
{ days: 30, bonus: 15 },
|
|
1185
|
-
{ days: 14, bonus: 10 },
|
|
1186
|
-
{ days: 7, bonus: 5 },
|
|
1187
|
-
{ days: 3, bonus: 2 },
|
|
1188
|
-
{ days: 1, bonus: 0 }
|
|
1189
|
-
],
|
|
1190
|
-
perfectScoreMultiplier: 1.5,
|
|
1191
|
-
firstAttemptBonus: 10,
|
|
1192
|
-
retryPenalty: 0.5,
|
|
1193
|
-
speedBonusMultiplier: 1.2,
|
|
1194
|
-
speedBonusThreshold: 0.8
|
|
1195
|
-
};
|
|
1196
|
-
|
|
1197
|
-
class XPEngine {
|
|
1198
|
-
config;
|
|
1199
|
-
constructor(config = {}) {
|
|
1200
|
-
this.config = {
|
|
1201
|
-
...DEFAULT_XP_CONFIG,
|
|
1202
|
-
...config,
|
|
1203
|
-
baseValues: { ...DEFAULT_XP_CONFIG.baseValues, ...config.baseValues },
|
|
1204
|
-
scoreThresholds: config.scoreThresholds || DEFAULT_XP_CONFIG.scoreThresholds,
|
|
1205
|
-
streakTiers: config.streakTiers || DEFAULT_XP_CONFIG.streakTiers
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
calculate(input) {
|
|
1209
|
-
const breakdown = [];
|
|
1210
|
-
const baseXp = input.baseXp ?? this.config.baseValues[input.activity];
|
|
1211
|
-
let totalXp = baseXp;
|
|
1212
|
-
breakdown.push({
|
|
1213
|
-
source: "base",
|
|
1214
|
-
amount: baseXp
|
|
1215
|
-
});
|
|
1216
|
-
if (input.score !== undefined) {
|
|
1217
|
-
const scoreMultiplier = this.getScoreMultiplier(input.score);
|
|
1218
|
-
if (scoreMultiplier !== 1) {
|
|
1219
|
-
const scoreBonus = Math.round(baseXp * (scoreMultiplier - 1));
|
|
1220
|
-
totalXp += scoreBonus;
|
|
1221
|
-
breakdown.push({
|
|
1222
|
-
source: "score_bonus",
|
|
1223
|
-
amount: scoreBonus,
|
|
1224
|
-
multiplier: scoreMultiplier
|
|
1225
|
-
});
|
|
1226
|
-
}
|
|
1227
|
-
if (input.score === 100) {
|
|
1228
|
-
const perfectBonus = Math.round(baseXp * (this.config.perfectScoreMultiplier - 1));
|
|
1229
|
-
totalXp += perfectBonus;
|
|
1230
|
-
breakdown.push({
|
|
1231
|
-
source: "perfect_score",
|
|
1232
|
-
amount: perfectBonus,
|
|
1233
|
-
multiplier: this.config.perfectScoreMultiplier
|
|
1234
|
-
});
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
if (input.attemptNumber === 1 && !input.isRetry) {
|
|
1238
|
-
totalXp += this.config.firstAttemptBonus;
|
|
1239
|
-
breakdown.push({
|
|
1240
|
-
source: "first_attempt",
|
|
1241
|
-
amount: this.config.firstAttemptBonus
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1244
|
-
if (input.isRetry) {
|
|
1245
|
-
const penalty = Math.round(totalXp * (1 - this.config.retryPenalty));
|
|
1246
|
-
totalXp -= penalty;
|
|
1247
|
-
breakdown.push({
|
|
1248
|
-
source: "retry_penalty",
|
|
1249
|
-
amount: -penalty,
|
|
1250
|
-
multiplier: this.config.retryPenalty
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
if (input.currentStreak && input.currentStreak > 0) {
|
|
1254
|
-
const streakBonus = this.getStreakBonus(input.currentStreak);
|
|
1255
|
-
if (streakBonus > 0) {
|
|
1256
|
-
totalXp += streakBonus;
|
|
1257
|
-
breakdown.push({
|
|
1258
|
-
source: "streak_bonus",
|
|
1259
|
-
amount: streakBonus
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
if (baseXp > 0) {
|
|
1264
|
-
totalXp = Math.max(1, totalXp);
|
|
1265
|
-
}
|
|
1266
|
-
return {
|
|
1267
|
-
totalXp: Math.round(totalXp),
|
|
1268
|
-
baseXp,
|
|
1269
|
-
breakdown
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
calculateStreakBonus(currentStreak) {
|
|
1273
|
-
const bonus = this.getStreakBonus(currentStreak);
|
|
1274
|
-
return {
|
|
1275
|
-
totalXp: bonus,
|
|
1276
|
-
baseXp: bonus,
|
|
1277
|
-
breakdown: [
|
|
1278
|
-
{
|
|
1279
|
-
source: "streak_bonus",
|
|
1280
|
-
amount: bonus
|
|
1281
|
-
}
|
|
1282
|
-
]
|
|
1283
|
-
};
|
|
1284
|
-
}
|
|
1285
|
-
getXpForLevel(level) {
|
|
1286
|
-
if (level <= 1)
|
|
1287
|
-
return 0;
|
|
1288
|
-
return Math.round(100 * Math.pow(level - 1, 1.5));
|
|
1289
|
-
}
|
|
1290
|
-
getLevelFromXp(totalXp) {
|
|
1291
|
-
let level = 1;
|
|
1292
|
-
let xpRequired = this.getXpForLevel(level + 1);
|
|
1293
|
-
while (totalXp >= xpRequired && level < 1000) {
|
|
1294
|
-
level++;
|
|
1295
|
-
xpRequired = this.getXpForLevel(level + 1);
|
|
1296
|
-
}
|
|
1297
|
-
const xpForCurrentLevel = this.getXpForLevel(level);
|
|
1298
|
-
const xpForNextLevel = this.getXpForLevel(level + 1);
|
|
1299
|
-
return {
|
|
1300
|
-
level,
|
|
1301
|
-
xpInLevel: totalXp - xpForCurrentLevel,
|
|
1302
|
-
xpForNextLevel: xpForNextLevel - xpForCurrentLevel
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
|
-
getScoreMultiplier(score) {
|
|
1306
|
-
for (const threshold of this.config.scoreThresholds) {
|
|
1307
|
-
if (score >= threshold.min) {
|
|
1308
|
-
return threshold.multiplier;
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
return 1;
|
|
1312
|
-
}
|
|
1313
|
-
getStreakBonus(streak) {
|
|
1314
|
-
for (const tier of this.config.streakTiers) {
|
|
1315
|
-
if (streak >= tier.days) {
|
|
1316
|
-
return tier.bonus;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
return 0;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
var SOURCE_KEY_MAP = {
|
|
1323
|
-
base: "xp.source.base",
|
|
1324
|
-
score_bonus: "xp.source.scoreBonus",
|
|
1325
|
-
perfect_score: "xp.source.perfectScore",
|
|
1326
|
-
first_attempt: "xp.source.firstAttempt",
|
|
1327
|
-
retry_penalty: "xp.source.retryPenalty",
|
|
1328
|
-
streak_bonus: "xp.source.streakBonus"
|
|
1329
|
-
};
|
|
1330
|
-
function getXpSourceLabel(source, locale) {
|
|
1331
|
-
const i18n = createLearningJourneyI18n(locale);
|
|
1332
|
-
const i18nKey = SOURCE_KEY_MAP[source];
|
|
1333
|
-
return i18nKey ? i18n.t(i18nKey) : source;
|
|
1334
|
-
}
|
|
1335
|
-
var xpEngine = new XPEngine;
|
|
1336
|
-
// src/entities/ai.ts
|
|
1337
|
-
import {
|
|
1338
|
-
defineEntity,
|
|
1339
|
-
defineEntityEnum,
|
|
1340
|
-
field,
|
|
1341
|
-
index
|
|
1342
|
-
} from "@contractspec/lib.schema";
|
|
1343
|
-
var LearningStyleEnum = defineEntityEnum({
|
|
1344
|
-
name: "LearningStyle",
|
|
1345
|
-
values: ["VISUAL", "AUDITORY", "READING", "KINESTHETIC", "MIXED"],
|
|
1346
|
-
schema: "lssm_learning",
|
|
1347
|
-
description: "Preferred learning style."
|
|
1348
|
-
});
|
|
1349
|
-
var RecommendationTypeEnum = defineEntityEnum({
|
|
1350
|
-
name: "RecommendationType",
|
|
1351
|
-
values: [
|
|
1352
|
-
"COURSE",
|
|
1353
|
-
"LESSON",
|
|
1354
|
-
"REVIEW",
|
|
1355
|
-
"PRACTICE",
|
|
1356
|
-
"ASSESSMENT",
|
|
1357
|
-
"DECK"
|
|
1358
|
-
],
|
|
1359
|
-
schema: "lssm_learning",
|
|
1360
|
-
description: "Type of learning recommendation."
|
|
1361
|
-
});
|
|
1362
|
-
var LearnerProfileEntity = defineEntity({
|
|
1363
|
-
name: "LearnerProfile",
|
|
1364
|
-
description: "AI personalization profile for a learner.",
|
|
1365
|
-
schema: "lssm_learning",
|
|
1366
|
-
map: "learner_profile",
|
|
1367
|
-
fields: {
|
|
1368
|
-
id: field.id({ description: "Unique profile identifier" }),
|
|
1369
|
-
learnerId: field.foreignKey({ description: "Learner" }),
|
|
1370
|
-
learningStyle: field.enum("LearningStyle", {
|
|
1371
|
-
default: "MIXED",
|
|
1372
|
-
description: "Preferred learning style"
|
|
1373
|
-
}),
|
|
1374
|
-
preferredDifficulty: field.string({
|
|
1375
|
-
default: '"adaptive"',
|
|
1376
|
-
description: "Difficulty preference"
|
|
1377
|
-
}),
|
|
1378
|
-
preferredSessionLength: field.int({
|
|
1379
|
-
default: 30,
|
|
1380
|
-
description: "Preferred session length in minutes"
|
|
1381
|
-
}),
|
|
1382
|
-
interests: field.json({ isOptional: true, description: "Topic interests" }),
|
|
1383
|
-
goals: field.json({ isOptional: true, description: "Learning goals" }),
|
|
1384
|
-
pacePreference: field.string({
|
|
1385
|
-
default: '"normal"',
|
|
1386
|
-
description: "Learning pace: slow, normal, fast"
|
|
1387
|
-
}),
|
|
1388
|
-
bestTimeOfDay: field.string({
|
|
1389
|
-
isOptional: true,
|
|
1390
|
-
description: "Best time for learning"
|
|
1391
|
-
}),
|
|
1392
|
-
averageSessionLength: field.int({
|
|
1393
|
-
isOptional: true,
|
|
1394
|
-
description: "Average session length"
|
|
1395
|
-
}),
|
|
1396
|
-
daysActivePerWeek: field.int({
|
|
1397
|
-
isOptional: true,
|
|
1398
|
-
description: "Days active per week"
|
|
1399
|
-
}),
|
|
1400
|
-
avgQuizScore: field.int({
|
|
1401
|
-
isOptional: true,
|
|
1402
|
-
description: "Average quiz score"
|
|
1403
|
-
}),
|
|
1404
|
-
avgLessonCompletionTime: field.int({
|
|
1405
|
-
isOptional: true,
|
|
1406
|
-
description: "Avg lesson completion time"
|
|
1407
|
-
}),
|
|
1408
|
-
strengths: field.json({
|
|
1409
|
-
isOptional: true,
|
|
1410
|
-
description: "Identified strengths"
|
|
1411
|
-
}),
|
|
1412
|
-
weaknesses: field.json({
|
|
1413
|
-
isOptional: true,
|
|
1414
|
-
description: "Areas for improvement"
|
|
1415
|
-
}),
|
|
1416
|
-
lastAnalyzedAt: field.dateTime({
|
|
1417
|
-
isOptional: true,
|
|
1418
|
-
description: "Last AI analysis"
|
|
1419
|
-
}),
|
|
1420
|
-
metadata: field.json({
|
|
1421
|
-
isOptional: true,
|
|
1422
|
-
description: "Additional metadata"
|
|
1423
|
-
}),
|
|
1424
|
-
createdAt: field.createdAt(),
|
|
1425
|
-
updatedAt: field.updatedAt(),
|
|
1426
|
-
learner: field.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
1427
|
-
onDelete: "Cascade"
|
|
1428
|
-
})
|
|
1429
|
-
},
|
|
1430
|
-
indexes: [
|
|
1431
|
-
index.unique(["learnerId"], { name: "learner_profile_unique" }),
|
|
1432
|
-
index.on(["learningStyle"])
|
|
1433
|
-
],
|
|
1434
|
-
enums: [LearningStyleEnum]
|
|
1435
|
-
});
|
|
1436
|
-
var SkillMapEntity = defineEntity({
|
|
1437
|
-
name: "SkillMap",
|
|
1438
|
-
description: "Maps learner proficiency across skills.",
|
|
1439
|
-
schema: "lssm_learning",
|
|
1440
|
-
map: "skill_map",
|
|
1441
|
-
fields: {
|
|
1442
|
-
id: field.id({ description: "Unique skill map identifier" }),
|
|
1443
|
-
learnerId: field.foreignKey({ description: "Learner" }),
|
|
1444
|
-
skillId: field.string({ description: "Skill identifier" }),
|
|
1445
|
-
skillName: field.string({ description: "Skill name" }),
|
|
1446
|
-
skillCategory: field.string({
|
|
1447
|
-
isOptional: true,
|
|
1448
|
-
description: "Skill category"
|
|
1449
|
-
}),
|
|
1450
|
-
level: field.int({ default: 0, description: "Proficiency level (0-100)" }),
|
|
1451
|
-
confidence: field.decimal({
|
|
1452
|
-
default: 0.5,
|
|
1453
|
-
description: "Confidence in assessment"
|
|
1454
|
-
}),
|
|
1455
|
-
lessonsCompleted: field.int({
|
|
1456
|
-
default: 0,
|
|
1457
|
-
description: "Related lessons completed"
|
|
1458
|
-
}),
|
|
1459
|
-
quizzesCompleted: field.int({
|
|
1460
|
-
default: 0,
|
|
1461
|
-
description: "Related quizzes completed"
|
|
1462
|
-
}),
|
|
1463
|
-
practiceTime: field.int({
|
|
1464
|
-
default: 0,
|
|
1465
|
-
description: "Practice time in minutes"
|
|
1466
|
-
}),
|
|
1467
|
-
lastPracticedAt: field.dateTime({
|
|
1468
|
-
isOptional: true,
|
|
1469
|
-
description: "Last practice time"
|
|
1470
|
-
}),
|
|
1471
|
-
learningVelocity: field.decimal({
|
|
1472
|
-
isOptional: true,
|
|
1473
|
-
description: "Learning speed for this skill"
|
|
1474
|
-
}),
|
|
1475
|
-
predictedTimeToMastery: field.int({
|
|
1476
|
-
isOptional: true,
|
|
1477
|
-
description: "Predicted time to mastery (minutes)"
|
|
1478
|
-
}),
|
|
1479
|
-
createdAt: field.createdAt(),
|
|
1480
|
-
updatedAt: field.updatedAt(),
|
|
1481
|
-
learner: field.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
1482
|
-
onDelete: "Cascade"
|
|
1483
|
-
})
|
|
1484
|
-
},
|
|
1485
|
-
indexes: [
|
|
1486
|
-
index.unique(["learnerId", "skillId"], { name: "skill_map_unique" }),
|
|
1487
|
-
index.on(["skillId", "level"]),
|
|
1488
|
-
index.on(["learnerId", "level"])
|
|
1489
|
-
]
|
|
1490
|
-
});
|
|
1491
|
-
var LearningPathEntity = defineEntity({
|
|
1492
|
-
name: "LearningPath",
|
|
1493
|
-
description: "AI-generated personalized learning path.",
|
|
1494
|
-
schema: "lssm_learning",
|
|
1495
|
-
map: "learning_path",
|
|
1496
|
-
fields: {
|
|
1497
|
-
id: field.id({ description: "Unique path identifier" }),
|
|
1498
|
-
learnerId: field.foreignKey({ description: "Learner" }),
|
|
1499
|
-
name: field.string({ description: "Path name" }),
|
|
1500
|
-
description: field.string({
|
|
1501
|
-
isOptional: true,
|
|
1502
|
-
description: "Path description"
|
|
1503
|
-
}),
|
|
1504
|
-
goal: field.string({ isOptional: true, description: "Path goal" }),
|
|
1505
|
-
steps: field.json({ description: "Ordered list of learning steps" }),
|
|
1506
|
-
currentStepIndex: field.int({
|
|
1507
|
-
default: 0,
|
|
1508
|
-
description: "Current step index"
|
|
1509
|
-
}),
|
|
1510
|
-
progress: field.int({ default: 0, description: "Completion percentage" }),
|
|
1511
|
-
completedSteps: field.int({ default: 0, description: "Steps completed" }),
|
|
1512
|
-
totalSteps: field.int({ default: 0, description: "Total steps" }),
|
|
1513
|
-
generatedAt: field.dateTime({ description: "When path was generated" }),
|
|
1514
|
-
adaptedFrom: field.string({
|
|
1515
|
-
isOptional: true,
|
|
1516
|
-
description: "Original path ID if adapted"
|
|
1517
|
-
}),
|
|
1518
|
-
generationParams: field.json({
|
|
1519
|
-
isOptional: true,
|
|
1520
|
-
description: "AI generation parameters"
|
|
1521
|
-
}),
|
|
1522
|
-
adaptationHistory: field.json({
|
|
1523
|
-
isOptional: true,
|
|
1524
|
-
description: "Path adaptation history"
|
|
1525
|
-
}),
|
|
1526
|
-
isActive: field.boolean({
|
|
1527
|
-
default: true,
|
|
1528
|
-
description: "Whether path is active"
|
|
1529
|
-
}),
|
|
1530
|
-
isCompleted: field.boolean({
|
|
1531
|
-
default: false,
|
|
1532
|
-
description: "Whether path is completed"
|
|
1533
|
-
}),
|
|
1534
|
-
startedAt: field.dateTime({
|
|
1535
|
-
isOptional: true,
|
|
1536
|
-
description: "When started"
|
|
1537
|
-
}),
|
|
1538
|
-
completedAt: field.dateTime({
|
|
1539
|
-
isOptional: true,
|
|
1540
|
-
description: "When completed"
|
|
1541
|
-
}),
|
|
1542
|
-
estimatedCompletionDate: field.dateTime({
|
|
1543
|
-
isOptional: true,
|
|
1544
|
-
description: "Estimated completion"
|
|
1545
|
-
}),
|
|
1546
|
-
createdAt: field.createdAt(),
|
|
1547
|
-
updatedAt: field.updatedAt(),
|
|
1548
|
-
learner: field.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
1549
|
-
onDelete: "Cascade"
|
|
1550
|
-
})
|
|
1551
|
-
},
|
|
1552
|
-
indexes: [index.on(["learnerId", "isActive"]), index.on(["generatedAt"])]
|
|
1553
|
-
});
|
|
1554
|
-
var RecommendationEntity = defineEntity({
|
|
1555
|
-
name: "Recommendation",
|
|
1556
|
-
description: "AI-powered learning recommendation.",
|
|
1557
|
-
schema: "lssm_learning",
|
|
1558
|
-
map: "recommendation",
|
|
1559
|
-
fields: {
|
|
1560
|
-
id: field.id({ description: "Unique recommendation identifier" }),
|
|
1561
|
-
learnerId: field.foreignKey({ description: "Learner" }),
|
|
1562
|
-
type: field.enum("RecommendationType", {
|
|
1563
|
-
description: "Recommendation type"
|
|
1564
|
-
}),
|
|
1565
|
-
itemId: field.string({ description: "Recommended item ID" }),
|
|
1566
|
-
itemType: field.string({
|
|
1567
|
-
description: "Item type (course, lesson, deck, etc.)"
|
|
1568
|
-
}),
|
|
1569
|
-
score: field.decimal({ description: "Recommendation score (0-1)" }),
|
|
1570
|
-
confidence: field.decimal({ description: "Confidence in recommendation" }),
|
|
1571
|
-
reason: field.string({ description: "Human-readable reason" }),
|
|
1572
|
-
factors: field.json({
|
|
1573
|
-
isOptional: true,
|
|
1574
|
-
description: "Factors that contributed to recommendation"
|
|
1575
|
-
}),
|
|
1576
|
-
status: field.string({
|
|
1577
|
-
default: '"pending"',
|
|
1578
|
-
description: "Status: pending, viewed, accepted, dismissed"
|
|
1579
|
-
}),
|
|
1580
|
-
viewedAt: field.dateTime({ isOptional: true, description: "When viewed" }),
|
|
1581
|
-
acceptedAt: field.dateTime({
|
|
1582
|
-
isOptional: true,
|
|
1583
|
-
description: "When accepted"
|
|
1584
|
-
}),
|
|
1585
|
-
dismissedAt: field.dateTime({
|
|
1586
|
-
isOptional: true,
|
|
1587
|
-
description: "When dismissed"
|
|
1588
|
-
}),
|
|
1589
|
-
feedback: field.string({ isOptional: true, description: "User feedback" }),
|
|
1590
|
-
feedbackRating: field.int({
|
|
1591
|
-
isOptional: true,
|
|
1592
|
-
description: "Feedback rating (1-5)"
|
|
1593
|
-
}),
|
|
1594
|
-
expiresAt: field.dateTime({
|
|
1595
|
-
isOptional: true,
|
|
1596
|
-
description: "When recommendation expires"
|
|
1597
|
-
}),
|
|
1598
|
-
createdAt: field.createdAt(),
|
|
1599
|
-
updatedAt: field.updatedAt(),
|
|
1600
|
-
learner: field.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
1601
|
-
onDelete: "Cascade"
|
|
1602
|
-
})
|
|
1603
|
-
},
|
|
1604
|
-
indexes: [
|
|
1605
|
-
index.on(["learnerId", "status", "score"]),
|
|
1606
|
-
index.on(["type", "status"]),
|
|
1607
|
-
index.on(["expiresAt"])
|
|
1608
|
-
],
|
|
1609
|
-
enums: [RecommendationTypeEnum]
|
|
1610
|
-
});
|
|
1611
|
-
var LearningGapEntity = defineEntity({
|
|
1612
|
-
name: "LearningGap",
|
|
1613
|
-
description: "Identified learning gap.",
|
|
1614
|
-
schema: "lssm_learning",
|
|
1615
|
-
map: "learning_gap",
|
|
1616
|
-
fields: {
|
|
1617
|
-
id: field.id({ description: "Unique gap identifier" }),
|
|
1618
|
-
learnerId: field.foreignKey({ description: "Learner" }),
|
|
1619
|
-
skillId: field.string({ description: "Skill with gap" }),
|
|
1620
|
-
skillName: field.string({ description: "Skill name" }),
|
|
1621
|
-
severity: field.string({
|
|
1622
|
-
default: '"moderate"',
|
|
1623
|
-
description: "Gap severity: minor, moderate, major"
|
|
1624
|
-
}),
|
|
1625
|
-
confidence: field.decimal({ description: "Confidence in gap detection" }),
|
|
1626
|
-
evidence: field.json({ isOptional: true, description: "Evidence for gap" }),
|
|
1627
|
-
relatedQuestions: field.json({
|
|
1628
|
-
isOptional: true,
|
|
1629
|
-
description: "Questions that revealed gap"
|
|
1630
|
-
}),
|
|
1631
|
-
suggestedRemediation: field.json({
|
|
1632
|
-
isOptional: true,
|
|
1633
|
-
description: "Suggested remediation"
|
|
1634
|
-
}),
|
|
1635
|
-
remediationProgress: field.int({
|
|
1636
|
-
default: 0,
|
|
1637
|
-
description: "Remediation progress"
|
|
1638
|
-
}),
|
|
1639
|
-
status: field.string({
|
|
1640
|
-
default: '"open"',
|
|
1641
|
-
description: "Status: open, in_progress, resolved"
|
|
1642
|
-
}),
|
|
1643
|
-
resolvedAt: field.dateTime({
|
|
1644
|
-
isOptional: true,
|
|
1645
|
-
description: "When resolved"
|
|
1646
|
-
}),
|
|
1647
|
-
detectedAt: field.dateTime({ description: "When gap was detected" }),
|
|
1648
|
-
createdAt: field.createdAt(),
|
|
1649
|
-
updatedAt: field.updatedAt(),
|
|
1650
|
-
learner: field.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
1651
|
-
onDelete: "Cascade"
|
|
1652
|
-
})
|
|
1653
|
-
},
|
|
1654
|
-
indexes: [
|
|
1655
|
-
index.on(["learnerId", "status"]),
|
|
1656
|
-
index.on(["skillId", "status"]),
|
|
1657
|
-
index.on(["severity", "status"])
|
|
1658
|
-
]
|
|
1659
|
-
});
|
|
1660
|
-
var aiEntities = [
|
|
1661
|
-
LearnerProfileEntity,
|
|
1662
|
-
SkillMapEntity,
|
|
1663
|
-
LearningPathEntity,
|
|
1664
|
-
RecommendationEntity,
|
|
1665
|
-
LearningGapEntity
|
|
1666
|
-
];
|
|
1667
|
-
var aiEnums = [LearningStyleEnum, RecommendationTypeEnum];
|
|
1668
|
-
|
|
1669
|
-
// src/entities/course.ts
|
|
1670
|
-
import {
|
|
1671
|
-
defineEntity as defineEntity2,
|
|
1672
|
-
defineEntityEnum as defineEntityEnum2,
|
|
1673
|
-
field as field2,
|
|
1674
|
-
index as index2
|
|
1675
|
-
} from "@contractspec/lib.schema";
|
|
1676
|
-
var CourseDifficultyEnum = defineEntityEnum2({
|
|
1677
|
-
name: "CourseDifficulty",
|
|
1678
|
-
values: ["BEGINNER", "INTERMEDIATE", "ADVANCED", "EXPERT"],
|
|
1679
|
-
schema: "lssm_learning",
|
|
1680
|
-
description: "Difficulty level of a course."
|
|
1681
|
-
});
|
|
1682
|
-
var CourseStatusEnum = defineEntityEnum2({
|
|
1683
|
-
name: "CourseStatus",
|
|
1684
|
-
values: ["DRAFT", "PUBLISHED", "ARCHIVED"],
|
|
1685
|
-
schema: "lssm_learning",
|
|
1686
|
-
description: "Publication status of a course."
|
|
1687
|
-
});
|
|
1688
|
-
var LessonTypeEnum = defineEntityEnum2({
|
|
1689
|
-
name: "LessonType",
|
|
1690
|
-
values: [
|
|
1691
|
-
"CONTENT",
|
|
1692
|
-
"VIDEO",
|
|
1693
|
-
"INTERACTIVE",
|
|
1694
|
-
"QUIZ",
|
|
1695
|
-
"PRACTICE",
|
|
1696
|
-
"PROJECT"
|
|
1697
|
-
],
|
|
1698
|
-
schema: "lssm_learning",
|
|
1699
|
-
description: "Type of lesson content."
|
|
1700
|
-
});
|
|
1701
|
-
var ContentTypeEnum = defineEntityEnum2({
|
|
1702
|
-
name: "ContentType",
|
|
1703
|
-
values: ["MARKDOWN", "VIDEO", "AUDIO", "EMBED", "SCORM", "CUSTOM"],
|
|
1704
|
-
schema: "lssm_learning",
|
|
1705
|
-
description: "Type of lesson content format."
|
|
1706
|
-
});
|
|
1707
|
-
var CourseEntity = defineEntity2({
|
|
1708
|
-
name: "Course",
|
|
1709
|
-
description: "A structured learning course.",
|
|
1710
|
-
schema: "lssm_learning",
|
|
1711
|
-
map: "course",
|
|
1712
|
-
fields: {
|
|
1713
|
-
id: field2.id({ description: "Unique course identifier" }),
|
|
1714
|
-
title: field2.string({ description: "Course title" }),
|
|
1715
|
-
slug: field2.string({ isUnique: true, description: "URL-friendly slug" }),
|
|
1716
|
-
description: field2.string({
|
|
1717
|
-
isOptional: true,
|
|
1718
|
-
description: "Course description"
|
|
1719
|
-
}),
|
|
1720
|
-
summary: field2.string({ isOptional: true, description: "Short summary" }),
|
|
1721
|
-
difficulty: field2.enum("CourseDifficulty", {
|
|
1722
|
-
default: "BEGINNER",
|
|
1723
|
-
description: "Difficulty level"
|
|
1724
|
-
}),
|
|
1725
|
-
category: field2.string({
|
|
1726
|
-
isOptional: true,
|
|
1727
|
-
description: "Course category"
|
|
1728
|
-
}),
|
|
1729
|
-
tags: field2.json({ isOptional: true, description: "Tags for discovery" }),
|
|
1730
|
-
prerequisites: field2.json({
|
|
1731
|
-
isOptional: true,
|
|
1732
|
-
description: "Required course IDs"
|
|
1733
|
-
}),
|
|
1734
|
-
requiredSkills: field2.json({
|
|
1735
|
-
isOptional: true,
|
|
1736
|
-
description: "Required skill levels"
|
|
1737
|
-
}),
|
|
1738
|
-
estimatedDuration: field2.int({
|
|
1739
|
-
isOptional: true,
|
|
1740
|
-
description: "Estimated duration in minutes"
|
|
1741
|
-
}),
|
|
1742
|
-
thumbnailUrl: field2.string({
|
|
1743
|
-
isOptional: true,
|
|
1744
|
-
description: "Thumbnail image URL"
|
|
1745
|
-
}),
|
|
1746
|
-
coverImageUrl: field2.string({
|
|
1747
|
-
isOptional: true,
|
|
1748
|
-
description: "Cover image URL"
|
|
1749
|
-
}),
|
|
1750
|
-
promoVideoUrl: field2.string({
|
|
1751
|
-
isOptional: true,
|
|
1752
|
-
description: "Promo video URL"
|
|
1753
|
-
}),
|
|
1754
|
-
status: field2.enum("CourseStatus", {
|
|
1755
|
-
default: "DRAFT",
|
|
1756
|
-
description: "Publication status"
|
|
1757
|
-
}),
|
|
1758
|
-
publishedAt: field2.dateTime({
|
|
1759
|
-
isOptional: true,
|
|
1760
|
-
description: "When published"
|
|
1761
|
-
}),
|
|
1762
|
-
authorId: field2.string({ description: "Author user ID" }),
|
|
1763
|
-
orgId: field2.string({
|
|
1764
|
-
isOptional: true,
|
|
1765
|
-
description: "Organization scope"
|
|
1766
|
-
}),
|
|
1767
|
-
isPublic: field2.boolean({
|
|
1768
|
-
default: false,
|
|
1769
|
-
description: "Whether course is publicly accessible"
|
|
1770
|
-
}),
|
|
1771
|
-
isFeatured: field2.boolean({
|
|
1772
|
-
default: false,
|
|
1773
|
-
description: "Whether course is featured"
|
|
1774
|
-
}),
|
|
1775
|
-
certificateEnabled: field2.boolean({
|
|
1776
|
-
default: false,
|
|
1777
|
-
description: "Award certificate on completion"
|
|
1778
|
-
}),
|
|
1779
|
-
metadata: field2.json({
|
|
1780
|
-
isOptional: true,
|
|
1781
|
-
description: "Additional metadata"
|
|
1782
|
-
}),
|
|
1783
|
-
createdAt: field2.createdAt(),
|
|
1784
|
-
updatedAt: field2.updatedAt(),
|
|
1785
|
-
modules: field2.hasMany("CourseModule"),
|
|
1786
|
-
enrollments: field2.hasMany("Enrollment")
|
|
1787
|
-
},
|
|
1788
|
-
indexes: [
|
|
1789
|
-
index2.on(["orgId", "status"]),
|
|
1790
|
-
index2.on(["category"]),
|
|
1791
|
-
index2.on(["difficulty"]),
|
|
1792
|
-
index2.on(["authorId"])
|
|
1793
|
-
],
|
|
1794
|
-
enums: [CourseDifficultyEnum, CourseStatusEnum]
|
|
1795
|
-
});
|
|
1796
|
-
var CourseModuleEntity = defineEntity2({
|
|
1797
|
-
name: "CourseModule",
|
|
1798
|
-
description: "A module (section) within a course.",
|
|
1799
|
-
schema: "lssm_learning",
|
|
1800
|
-
map: "course_module",
|
|
1801
|
-
fields: {
|
|
1802
|
-
id: field2.id({ description: "Unique module identifier" }),
|
|
1803
|
-
courseId: field2.foreignKey({ description: "Parent course" }),
|
|
1804
|
-
title: field2.string({ description: "Module title" }),
|
|
1805
|
-
description: field2.string({
|
|
1806
|
-
isOptional: true,
|
|
1807
|
-
description: "Module description"
|
|
1808
|
-
}),
|
|
1809
|
-
order: field2.int({ default: 0, description: "Display order" }),
|
|
1810
|
-
unlockCondition: field2.json({
|
|
1811
|
-
isOptional: true,
|
|
1812
|
-
description: "Conditions to unlock module"
|
|
1813
|
-
}),
|
|
1814
|
-
prerequisiteModuleIds: field2.json({
|
|
1815
|
-
isOptional: true,
|
|
1816
|
-
description: "Required modules to complete first"
|
|
1817
|
-
}),
|
|
1818
|
-
estimatedDuration: field2.int({
|
|
1819
|
-
isOptional: true,
|
|
1820
|
-
description: "Estimated duration in minutes"
|
|
1821
|
-
}),
|
|
1822
|
-
createdAt: field2.createdAt(),
|
|
1823
|
-
updatedAt: field2.updatedAt(),
|
|
1824
|
-
course: field2.belongsTo("Course", ["courseId"], ["id"], {
|
|
1825
|
-
onDelete: "Cascade"
|
|
1826
|
-
}),
|
|
1827
|
-
lessons: field2.hasMany("Lesson"),
|
|
1828
|
-
completions: field2.hasMany("ModuleCompletion")
|
|
1829
|
-
},
|
|
1830
|
-
indexes: [index2.on(["courseId", "order"])]
|
|
1831
|
-
});
|
|
1832
|
-
var LessonEntity = defineEntity2({
|
|
1833
|
-
name: "Lesson",
|
|
1834
|
-
description: "An individual lesson within a module.",
|
|
1835
|
-
schema: "lssm_learning",
|
|
1836
|
-
map: "lesson",
|
|
1837
|
-
fields: {
|
|
1838
|
-
id: field2.id({ description: "Unique lesson identifier" }),
|
|
1839
|
-
moduleId: field2.foreignKey({ description: "Parent module" }),
|
|
1840
|
-
title: field2.string({ description: "Lesson title" }),
|
|
1841
|
-
description: field2.string({
|
|
1842
|
-
isOptional: true,
|
|
1843
|
-
description: "Lesson description"
|
|
1844
|
-
}),
|
|
1845
|
-
type: field2.enum("LessonType", {
|
|
1846
|
-
default: "CONTENT",
|
|
1847
|
-
description: "Lesson type"
|
|
1848
|
-
}),
|
|
1849
|
-
order: field2.int({ default: 0, description: "Display order" }),
|
|
1850
|
-
estimatedDuration: field2.int({
|
|
1851
|
-
isOptional: true,
|
|
1852
|
-
description: "Estimated duration in minutes"
|
|
1853
|
-
}),
|
|
1854
|
-
xpReward: field2.int({
|
|
1855
|
-
default: 10,
|
|
1856
|
-
description: "XP awarded on completion"
|
|
1857
|
-
}),
|
|
1858
|
-
isFree: field2.boolean({
|
|
1859
|
-
default: false,
|
|
1860
|
-
description: "Whether lesson is free preview"
|
|
1861
|
-
}),
|
|
1862
|
-
isRequired: field2.boolean({
|
|
1863
|
-
default: true,
|
|
1864
|
-
description: "Whether lesson is required for completion"
|
|
1865
|
-
}),
|
|
1866
|
-
metadata: field2.json({
|
|
1867
|
-
isOptional: true,
|
|
1868
|
-
description: "Additional metadata"
|
|
1869
|
-
}),
|
|
1870
|
-
createdAt: field2.createdAt(),
|
|
1871
|
-
updatedAt: field2.updatedAt(),
|
|
1872
|
-
module: field2.belongsTo("CourseModule", ["moduleId"], ["id"], {
|
|
1873
|
-
onDelete: "Cascade"
|
|
1874
|
-
}),
|
|
1875
|
-
contents: field2.hasMany("LessonContent"),
|
|
1876
|
-
progress: field2.hasMany("LessonProgress"),
|
|
1877
|
-
quizzes: field2.hasMany("Quiz")
|
|
1878
|
-
},
|
|
1879
|
-
indexes: [index2.on(["moduleId", "order"]), index2.on(["type"])],
|
|
1880
|
-
enums: [LessonTypeEnum]
|
|
1881
|
-
});
|
|
1882
|
-
var LessonContentEntity = defineEntity2({
|
|
1883
|
-
name: "LessonContent",
|
|
1884
|
-
description: "Content block within a lesson.",
|
|
1885
|
-
schema: "lssm_learning",
|
|
1886
|
-
map: "lesson_content",
|
|
1887
|
-
fields: {
|
|
1888
|
-
id: field2.id({ description: "Unique content identifier" }),
|
|
1889
|
-
lessonId: field2.foreignKey({ description: "Parent lesson" }),
|
|
1890
|
-
contentType: field2.enum("ContentType", { description: "Content format" }),
|
|
1891
|
-
content: field2.string({
|
|
1892
|
-
isOptional: true,
|
|
1893
|
-
description: "Text content (markdown, etc.)"
|
|
1894
|
-
}),
|
|
1895
|
-
mediaUrl: field2.string({
|
|
1896
|
-
isOptional: true,
|
|
1897
|
-
description: "Media URL for video/audio"
|
|
1898
|
-
}),
|
|
1899
|
-
embedData: field2.json({
|
|
1900
|
-
isOptional: true,
|
|
1901
|
-
description: "Embed data for external content"
|
|
1902
|
-
}),
|
|
1903
|
-
order: field2.int({ default: 0, description: "Display order" }),
|
|
1904
|
-
duration: field2.int({
|
|
1905
|
-
isOptional: true,
|
|
1906
|
-
description: "Content duration in seconds"
|
|
1907
|
-
}),
|
|
1908
|
-
metadata: field2.json({
|
|
1909
|
-
isOptional: true,
|
|
1910
|
-
description: "Additional metadata"
|
|
1911
|
-
}),
|
|
1912
|
-
createdAt: field2.createdAt(),
|
|
1913
|
-
updatedAt: field2.updatedAt(),
|
|
1914
|
-
lesson: field2.belongsTo("Lesson", ["lessonId"], ["id"], {
|
|
1915
|
-
onDelete: "Cascade"
|
|
1916
|
-
})
|
|
1917
|
-
},
|
|
1918
|
-
indexes: [index2.on(["lessonId", "order"])],
|
|
1919
|
-
enums: [ContentTypeEnum]
|
|
1920
|
-
});
|
|
1921
|
-
var courseEntities = [
|
|
1922
|
-
CourseEntity,
|
|
1923
|
-
CourseModuleEntity,
|
|
1924
|
-
LessonEntity,
|
|
1925
|
-
LessonContentEntity
|
|
1926
|
-
];
|
|
1927
|
-
var courseEnums = [
|
|
1928
|
-
CourseDifficultyEnum,
|
|
1929
|
-
CourseStatusEnum,
|
|
1930
|
-
LessonTypeEnum,
|
|
1931
|
-
ContentTypeEnum
|
|
1932
|
-
];
|
|
1933
|
-
|
|
1934
|
-
// src/entities/flashcard.ts
|
|
1935
|
-
import {
|
|
1936
|
-
defineEntity as defineEntity3,
|
|
1937
|
-
defineEntityEnum as defineEntityEnum3,
|
|
1938
|
-
field as field3,
|
|
1939
|
-
index as index3
|
|
1940
|
-
} from "@contractspec/lib.schema";
|
|
1941
|
-
var CardRatingEnum = defineEntityEnum3({
|
|
1942
|
-
name: "CardRating",
|
|
1943
|
-
values: ["AGAIN", "HARD", "GOOD", "EASY"],
|
|
1944
|
-
schema: "lssm_learning",
|
|
1945
|
-
description: "Rating for a flashcard review."
|
|
1946
|
-
});
|
|
1947
|
-
var DeckEntity = defineEntity3({
|
|
1948
|
-
name: "Deck",
|
|
1949
|
-
description: "A collection of flashcards.",
|
|
1950
|
-
schema: "lssm_learning",
|
|
1951
|
-
map: "deck",
|
|
1952
|
-
fields: {
|
|
1953
|
-
id: field3.id({ description: "Unique deck identifier" }),
|
|
1954
|
-
ownerId: field3.foreignKey({ description: "Deck owner (learner)" }),
|
|
1955
|
-
title: field3.string({ description: "Deck title" }),
|
|
1956
|
-
description: field3.string({
|
|
1957
|
-
isOptional: true,
|
|
1958
|
-
description: "Deck description"
|
|
1959
|
-
}),
|
|
1960
|
-
category: field3.string({ isOptional: true, description: "Deck category" }),
|
|
1961
|
-
tags: field3.json({ isOptional: true, description: "Tags for discovery" }),
|
|
1962
|
-
isPublic: field3.boolean({
|
|
1963
|
-
default: false,
|
|
1964
|
-
description: "Whether deck is publicly visible"
|
|
1965
|
-
}),
|
|
1966
|
-
cardCount: field3.int({ default: 0, description: "Number of cards" }),
|
|
1967
|
-
coverImageUrl: field3.string({
|
|
1968
|
-
isOptional: true,
|
|
1969
|
-
description: "Cover image URL"
|
|
1970
|
-
}),
|
|
1971
|
-
orgId: field3.string({
|
|
1972
|
-
isOptional: true,
|
|
1973
|
-
description: "Organization scope"
|
|
1974
|
-
}),
|
|
1975
|
-
newCardsPerDay: field3.int({
|
|
1976
|
-
default: 20,
|
|
1977
|
-
description: "New cards to introduce per day"
|
|
1978
|
-
}),
|
|
1979
|
-
reviewsPerDay: field3.int({
|
|
1980
|
-
default: 100,
|
|
1981
|
-
description: "Maximum reviews per day"
|
|
1982
|
-
}),
|
|
1983
|
-
metadata: field3.json({
|
|
1984
|
-
isOptional: true,
|
|
1985
|
-
description: "Additional metadata"
|
|
1986
|
-
}),
|
|
1987
|
-
createdAt: field3.createdAt(),
|
|
1988
|
-
updatedAt: field3.updatedAt(),
|
|
1989
|
-
owner: field3.belongsTo("Learner", ["ownerId"], ["id"], {
|
|
1990
|
-
onDelete: "Cascade"
|
|
1991
|
-
}),
|
|
1992
|
-
cards: field3.hasMany("Card")
|
|
1993
|
-
},
|
|
1994
|
-
indexes: [
|
|
1995
|
-
index3.on(["ownerId"]),
|
|
1996
|
-
index3.on(["isPublic", "category"]),
|
|
1997
|
-
index3.on(["orgId"])
|
|
1998
|
-
]
|
|
1999
|
-
});
|
|
2000
|
-
var CardEntity = defineEntity3({
|
|
2001
|
-
name: "Card",
|
|
2002
|
-
description: "An individual flashcard.",
|
|
2003
|
-
schema: "lssm_learning",
|
|
2004
|
-
map: "card",
|
|
2005
|
-
fields: {
|
|
2006
|
-
id: field3.id({ description: "Unique card identifier" }),
|
|
2007
|
-
deckId: field3.foreignKey({ description: "Parent deck" }),
|
|
2008
|
-
front: field3.string({ description: "Front of card (question)" }),
|
|
2009
|
-
back: field3.string({ description: "Back of card (answer)" }),
|
|
2010
|
-
hints: field3.json({ isOptional: true, description: "Hints for the card" }),
|
|
2011
|
-
explanation: field3.string({
|
|
2012
|
-
isOptional: true,
|
|
2013
|
-
description: "Detailed explanation"
|
|
2014
|
-
}),
|
|
2015
|
-
frontMediaUrl: field3.string({
|
|
2016
|
-
isOptional: true,
|
|
2017
|
-
description: "Media for front"
|
|
2018
|
-
}),
|
|
2019
|
-
backMediaUrl: field3.string({
|
|
2020
|
-
isOptional: true,
|
|
2021
|
-
description: "Media for back"
|
|
2022
|
-
}),
|
|
2023
|
-
audioUrl: field3.string({
|
|
2024
|
-
isOptional: true,
|
|
2025
|
-
description: "Audio pronunciation"
|
|
2026
|
-
}),
|
|
2027
|
-
tags: field3.json({ isOptional: true, description: "Card tags" }),
|
|
2028
|
-
difficulty: field3.int({ default: 0, description: "Card difficulty (0-5)" }),
|
|
2029
|
-
order: field3.int({ default: 0, description: "Card order in deck" }),
|
|
2030
|
-
isSuspended: field3.boolean({
|
|
2031
|
-
default: false,
|
|
2032
|
-
description: "Whether card is suspended"
|
|
2033
|
-
}),
|
|
2034
|
-
metadata: field3.json({
|
|
2035
|
-
isOptional: true,
|
|
2036
|
-
description: "Additional metadata"
|
|
2037
|
-
}),
|
|
2038
|
-
createdAt: field3.createdAt(),
|
|
2039
|
-
updatedAt: field3.updatedAt(),
|
|
2040
|
-
deck: field3.belongsTo("Deck", ["deckId"], ["id"], { onDelete: "Cascade" }),
|
|
2041
|
-
reviews: field3.hasMany("CardReview"),
|
|
2042
|
-
schedules: field3.hasMany("CardSchedule")
|
|
2043
|
-
},
|
|
2044
|
-
indexes: [index3.on(["deckId", "order"]), index3.on(["isSuspended"])]
|
|
2045
|
-
});
|
|
2046
|
-
var CardReviewEntity = defineEntity3({
|
|
2047
|
-
name: "CardReview",
|
|
2048
|
-
description: "A single review of a flashcard.",
|
|
2049
|
-
schema: "lssm_learning",
|
|
2050
|
-
map: "card_review",
|
|
2051
|
-
fields: {
|
|
2052
|
-
id: field3.id({ description: "Unique review identifier" }),
|
|
2053
|
-
learnerId: field3.foreignKey({ description: "Reviewer" }),
|
|
2054
|
-
cardId: field3.foreignKey({ description: "Reviewed card" }),
|
|
2055
|
-
rating: field3.enum("CardRating", { description: "Review rating" }),
|
|
2056
|
-
responseTimeMs: field3.int({
|
|
2057
|
-
isOptional: true,
|
|
2058
|
-
description: "Time to respond in ms"
|
|
2059
|
-
}),
|
|
2060
|
-
intervalBefore: field3.int({ description: "Interval before review (days)" }),
|
|
2061
|
-
easeFactorBefore: field3.decimal({
|
|
2062
|
-
description: "Ease factor before review"
|
|
2063
|
-
}),
|
|
2064
|
-
intervalAfter: field3.int({ description: "Interval after review (days)" }),
|
|
2065
|
-
easeFactorAfter: field3.decimal({ description: "Ease factor after review" }),
|
|
2066
|
-
reviewType: field3.string({
|
|
2067
|
-
default: '"review"',
|
|
2068
|
-
description: "Type: new, learning, review, relearning"
|
|
2069
|
-
}),
|
|
2070
|
-
reviewedAt: field3.dateTime({ description: "When reviewed" }),
|
|
2071
|
-
createdAt: field3.createdAt(),
|
|
2072
|
-
learner: field3.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2073
|
-
onDelete: "Cascade"
|
|
2074
|
-
}),
|
|
2075
|
-
card: field3.belongsTo("Card", ["cardId"], ["id"], { onDelete: "Cascade" })
|
|
2076
|
-
},
|
|
2077
|
-
indexes: [
|
|
2078
|
-
index3.on(["learnerId", "reviewedAt"]),
|
|
2079
|
-
index3.on(["cardId", "reviewedAt"]),
|
|
2080
|
-
index3.on(["rating"])
|
|
2081
|
-
],
|
|
2082
|
-
enums: [CardRatingEnum]
|
|
2083
|
-
});
|
|
2084
|
-
var CardScheduleEntity = defineEntity3({
|
|
2085
|
-
name: "CardSchedule",
|
|
2086
|
-
description: "SRS schedule for a learner/card pair.",
|
|
2087
|
-
schema: "lssm_learning",
|
|
2088
|
-
map: "card_schedule",
|
|
2089
|
-
fields: {
|
|
2090
|
-
id: field3.id({ description: "Unique schedule identifier" }),
|
|
2091
|
-
learnerId: field3.foreignKey({ description: "Learner" }),
|
|
2092
|
-
cardId: field3.foreignKey({ description: "Card" }),
|
|
2093
|
-
interval: field3.int({
|
|
2094
|
-
default: 0,
|
|
2095
|
-
description: "Current interval in days"
|
|
2096
|
-
}),
|
|
2097
|
-
easeFactor: field3.decimal({
|
|
2098
|
-
default: 2.5,
|
|
2099
|
-
description: "Ease factor (SM-2)"
|
|
2100
|
-
}),
|
|
2101
|
-
repetitions: field3.int({
|
|
2102
|
-
default: 0,
|
|
2103
|
-
description: "Number of successful repetitions"
|
|
2104
|
-
}),
|
|
2105
|
-
nextReviewAt: field3.dateTime({ description: "When next review is due" }),
|
|
2106
|
-
lastReviewAt: field3.dateTime({
|
|
2107
|
-
isOptional: true,
|
|
2108
|
-
description: "When last reviewed"
|
|
2109
|
-
}),
|
|
2110
|
-
learningStep: field3.int({
|
|
2111
|
-
default: 0,
|
|
2112
|
-
description: "Current learning step"
|
|
2113
|
-
}),
|
|
2114
|
-
isGraduated: field3.boolean({
|
|
2115
|
-
default: false,
|
|
2116
|
-
description: "Whether card has graduated"
|
|
2117
|
-
}),
|
|
2118
|
-
isRelearning: field3.boolean({
|
|
2119
|
-
default: false,
|
|
2120
|
-
description: "Whether card is being relearned"
|
|
2121
|
-
}),
|
|
2122
|
-
lapses: field3.int({
|
|
2123
|
-
default: 0,
|
|
2124
|
-
description: "Number of times card was forgotten"
|
|
2125
|
-
}),
|
|
2126
|
-
reviewCount: field3.int({ default: 0, description: "Total review count" }),
|
|
2127
|
-
createdAt: field3.createdAt(),
|
|
2128
|
-
updatedAt: field3.updatedAt(),
|
|
2129
|
-
learner: field3.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2130
|
-
onDelete: "Cascade"
|
|
2131
|
-
}),
|
|
2132
|
-
card: field3.belongsTo("Card", ["cardId"], ["id"], { onDelete: "Cascade" })
|
|
2133
|
-
},
|
|
2134
|
-
indexes: [
|
|
2135
|
-
index3.unique(["learnerId", "cardId"], { name: "card_schedule_unique" }),
|
|
2136
|
-
index3.on(["learnerId", "nextReviewAt"]),
|
|
2137
|
-
index3.on(["nextReviewAt"])
|
|
2138
|
-
]
|
|
2139
|
-
});
|
|
2140
|
-
var flashcardEntities = [
|
|
2141
|
-
DeckEntity,
|
|
2142
|
-
CardEntity,
|
|
2143
|
-
CardReviewEntity,
|
|
2144
|
-
CardScheduleEntity
|
|
2145
|
-
];
|
|
2146
|
-
var flashcardEnums = [CardRatingEnum];
|
|
2147
|
-
|
|
2148
|
-
// src/entities/gamification.ts
|
|
2149
|
-
import {
|
|
2150
|
-
defineEntity as defineEntity4,
|
|
2151
|
-
defineEntityEnum as defineEntityEnum4,
|
|
2152
|
-
field as field4,
|
|
2153
|
-
index as index4
|
|
2154
|
-
} from "@contractspec/lib.schema";
|
|
2155
|
-
var AchievementTypeEnum = defineEntityEnum4({
|
|
2156
|
-
name: "AchievementType",
|
|
2157
|
-
values: [
|
|
2158
|
-
"MILESTONE",
|
|
2159
|
-
"STREAK",
|
|
2160
|
-
"SKILL",
|
|
2161
|
-
"SOCIAL",
|
|
2162
|
-
"SPECIAL",
|
|
2163
|
-
"SEASONAL"
|
|
2164
|
-
],
|
|
2165
|
-
schema: "lssm_learning",
|
|
2166
|
-
description: "Type of achievement."
|
|
2167
|
-
});
|
|
2168
|
-
var LeaderboardPeriodEnum = defineEntityEnum4({
|
|
2169
|
-
name: "LeaderboardPeriod",
|
|
2170
|
-
values: ["DAILY", "WEEKLY", "MONTHLY", "ALL_TIME"],
|
|
2171
|
-
schema: "lssm_learning",
|
|
2172
|
-
description: "Leaderboard time period."
|
|
2173
|
-
});
|
|
2174
|
-
var AchievementEntity = defineEntity4({
|
|
2175
|
-
name: "Achievement",
|
|
2176
|
-
description: "An achievement that can be unlocked.",
|
|
2177
|
-
schema: "lssm_learning",
|
|
2178
|
-
map: "achievement",
|
|
2179
|
-
fields: {
|
|
2180
|
-
id: field4.id({ description: "Unique achievement identifier" }),
|
|
2181
|
-
key: field4.string({ isUnique: true, description: "Achievement key" }),
|
|
2182
|
-
name: field4.string({ description: "Achievement name" }),
|
|
2183
|
-
description: field4.string({ description: "Achievement description" }),
|
|
2184
|
-
icon: field4.string({ isOptional: true, description: "Icon name or URL" }),
|
|
2185
|
-
color: field4.string({ isOptional: true, description: "Display color" }),
|
|
2186
|
-
badgeUrl: field4.string({
|
|
2187
|
-
isOptional: true,
|
|
2188
|
-
description: "Badge image URL"
|
|
2189
|
-
}),
|
|
2190
|
-
type: field4.enum("AchievementType", {
|
|
2191
|
-
default: "MILESTONE",
|
|
2192
|
-
description: "Achievement type"
|
|
2193
|
-
}),
|
|
2194
|
-
category: field4.string({
|
|
2195
|
-
isOptional: true,
|
|
2196
|
-
description: "Achievement category"
|
|
2197
|
-
}),
|
|
2198
|
-
rarity: field4.string({
|
|
2199
|
-
default: '"common"',
|
|
2200
|
-
description: "Rarity: common, rare, epic, legendary"
|
|
2201
|
-
}),
|
|
2202
|
-
xpReward: field4.int({ default: 50, description: "XP awarded" }),
|
|
2203
|
-
condition: field4.json({ description: "Unlock condition" }),
|
|
2204
|
-
order: field4.int({ default: 0, description: "Display order" }),
|
|
2205
|
-
isHidden: field4.boolean({
|
|
2206
|
-
default: false,
|
|
2207
|
-
description: "Hide until unlocked"
|
|
2208
|
-
}),
|
|
2209
|
-
isActive: field4.boolean({
|
|
2210
|
-
default: true,
|
|
2211
|
-
description: "Whether achievement is active"
|
|
2212
|
-
}),
|
|
2213
|
-
orgId: field4.string({
|
|
2214
|
-
isOptional: true,
|
|
2215
|
-
description: "Organization scope"
|
|
2216
|
-
}),
|
|
2217
|
-
metadata: field4.json({
|
|
2218
|
-
isOptional: true,
|
|
2219
|
-
description: "Additional metadata"
|
|
2220
|
-
}),
|
|
2221
|
-
createdAt: field4.createdAt(),
|
|
2222
|
-
updatedAt: field4.updatedAt(),
|
|
2223
|
-
learnerAchievements: field4.hasMany("LearnerAchievement")
|
|
2224
|
-
},
|
|
2225
|
-
indexes: [
|
|
2226
|
-
index4.on(["type"]),
|
|
2227
|
-
index4.on(["category"]),
|
|
2228
|
-
index4.on(["isActive"]),
|
|
2229
|
-
index4.on(["orgId"])
|
|
2230
|
-
],
|
|
2231
|
-
enums: [AchievementTypeEnum]
|
|
2232
|
-
});
|
|
2233
|
-
var LearnerAchievementEntity = defineEntity4({
|
|
2234
|
-
name: "LearnerAchievement",
|
|
2235
|
-
description: "An achievement unlocked by a learner.",
|
|
2236
|
-
schema: "lssm_learning",
|
|
2237
|
-
map: "learner_achievement",
|
|
2238
|
-
fields: {
|
|
2239
|
-
id: field4.id({ description: "Unique record identifier" }),
|
|
2240
|
-
learnerId: field4.foreignKey({ description: "Learner" }),
|
|
2241
|
-
achievementId: field4.foreignKey({ description: "Achievement" }),
|
|
2242
|
-
xpEarned: field4.int({ default: 0, description: "XP earned" }),
|
|
2243
|
-
progress: field4.int({ default: 100, description: "Progress percentage" }),
|
|
2244
|
-
currentValue: field4.int({
|
|
2245
|
-
isOptional: true,
|
|
2246
|
-
description: "Current value towards goal"
|
|
2247
|
-
}),
|
|
2248
|
-
targetValue: field4.int({
|
|
2249
|
-
isOptional: true,
|
|
2250
|
-
description: "Target value for completion"
|
|
2251
|
-
}),
|
|
2252
|
-
unlockedAt: field4.dateTime({ description: "When unlocked" }),
|
|
2253
|
-
createdAt: field4.createdAt(),
|
|
2254
|
-
learner: field4.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2255
|
-
onDelete: "Cascade"
|
|
2256
|
-
}),
|
|
2257
|
-
achievement: field4.belongsTo("Achievement", ["achievementId"], ["id"], {
|
|
2258
|
-
onDelete: "Cascade"
|
|
2259
|
-
})
|
|
2260
|
-
},
|
|
2261
|
-
indexes: [
|
|
2262
|
-
index4.unique(["learnerId", "achievementId"], {
|
|
2263
|
-
name: "learner_achievement_unique"
|
|
2264
|
-
}),
|
|
2265
|
-
index4.on(["learnerId", "unlockedAt"]),
|
|
2266
|
-
index4.on(["achievementId"])
|
|
2267
|
-
]
|
|
2268
|
-
});
|
|
2269
|
-
var StreakEntity = defineEntity4({
|
|
2270
|
-
name: "Streak",
|
|
2271
|
-
description: "Tracks daily learning streaks.",
|
|
2272
|
-
schema: "lssm_learning",
|
|
2273
|
-
map: "streak",
|
|
2274
|
-
fields: {
|
|
2275
|
-
id: field4.id({ description: "Unique streak identifier" }),
|
|
2276
|
-
learnerId: field4.foreignKey({ description: "Learner" }),
|
|
2277
|
-
currentStreak: field4.int({
|
|
2278
|
-
default: 0,
|
|
2279
|
-
description: "Current streak days"
|
|
2280
|
-
}),
|
|
2281
|
-
longestStreak: field4.int({
|
|
2282
|
-
default: 0,
|
|
2283
|
-
description: "Longest streak ever"
|
|
2284
|
-
}),
|
|
2285
|
-
lastActivityAt: field4.dateTime({
|
|
2286
|
-
isOptional: true,
|
|
2287
|
-
description: "Last learning activity"
|
|
2288
|
-
}),
|
|
2289
|
-
lastActivityDate: field4.string({
|
|
2290
|
-
isOptional: true,
|
|
2291
|
-
description: "Last activity date (YYYY-MM-DD)"
|
|
2292
|
-
}),
|
|
2293
|
-
freezesRemaining: field4.int({
|
|
2294
|
-
default: 0,
|
|
2295
|
-
description: "Streak freezes available"
|
|
2296
|
-
}),
|
|
2297
|
-
freezeUsedAt: field4.dateTime({
|
|
2298
|
-
isOptional: true,
|
|
2299
|
-
description: "When last freeze was used"
|
|
2300
|
-
}),
|
|
2301
|
-
streakHistory: field4.json({
|
|
2302
|
-
isOptional: true,
|
|
2303
|
-
description: "Historical streak data"
|
|
2304
|
-
}),
|
|
2305
|
-
createdAt: field4.createdAt(),
|
|
2306
|
-
updatedAt: field4.updatedAt(),
|
|
2307
|
-
learner: field4.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2308
|
-
onDelete: "Cascade"
|
|
2309
|
-
})
|
|
2310
|
-
},
|
|
2311
|
-
indexes: [
|
|
2312
|
-
index4.unique(["learnerId"], { name: "streak_learner_unique" }),
|
|
2313
|
-
index4.on(["currentStreak"]),
|
|
2314
|
-
index4.on(["longestStreak"])
|
|
2315
|
-
]
|
|
2316
|
-
});
|
|
2317
|
-
var DailyGoalEntity = defineEntity4({
|
|
2318
|
-
name: "DailyGoal",
|
|
2319
|
-
description: "Daily XP goal tracking.",
|
|
2320
|
-
schema: "lssm_learning",
|
|
2321
|
-
map: "daily_goal",
|
|
2322
|
-
fields: {
|
|
2323
|
-
id: field4.id({ description: "Unique goal identifier" }),
|
|
2324
|
-
learnerId: field4.foreignKey({ description: "Learner" }),
|
|
2325
|
-
date: field4.string({ description: "Date (YYYY-MM-DD)" }),
|
|
2326
|
-
targetXp: field4.int({ description: "Target XP for the day" }),
|
|
2327
|
-
currentXp: field4.int({ default: 0, description: "XP earned today" }),
|
|
2328
|
-
isCompleted: field4.boolean({
|
|
2329
|
-
default: false,
|
|
2330
|
-
description: "Whether goal was met"
|
|
2331
|
-
}),
|
|
2332
|
-
completedAt: field4.dateTime({
|
|
2333
|
-
isOptional: true,
|
|
2334
|
-
description: "When goal was completed"
|
|
2335
|
-
}),
|
|
2336
|
-
xpBreakdown: field4.json({
|
|
2337
|
-
isOptional: true,
|
|
2338
|
-
description: "XP sources breakdown"
|
|
2339
|
-
}),
|
|
2340
|
-
createdAt: field4.createdAt(),
|
|
2341
|
-
updatedAt: field4.updatedAt(),
|
|
2342
|
-
learner: field4.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2343
|
-
onDelete: "Cascade"
|
|
2344
|
-
})
|
|
2345
|
-
},
|
|
2346
|
-
indexes: [
|
|
2347
|
-
index4.unique(["learnerId", "date"], { name: "daily_goal_unique" }),
|
|
2348
|
-
index4.on(["date", "isCompleted"])
|
|
2349
|
-
]
|
|
2350
|
-
});
|
|
2351
|
-
var LeaderboardEntryEntity = defineEntity4({
|
|
2352
|
-
name: "LeaderboardEntry",
|
|
2353
|
-
description: "Leaderboard entry for a learner.",
|
|
2354
|
-
schema: "lssm_learning",
|
|
2355
|
-
map: "leaderboard_entry",
|
|
2356
|
-
fields: {
|
|
2357
|
-
id: field4.id({ description: "Unique entry identifier" }),
|
|
2358
|
-
learnerId: field4.foreignKey({ description: "Learner" }),
|
|
2359
|
-
periodType: field4.enum("LeaderboardPeriod", { description: "Period type" }),
|
|
2360
|
-
periodKey: field4.string({ description: "Period key (e.g., 2024-W01)" }),
|
|
2361
|
-
xp: field4.int({ default: 0, description: "XP earned in period" }),
|
|
2362
|
-
rank: field4.int({ isOptional: true, description: "Rank in leaderboard" }),
|
|
2363
|
-
lessonsCompleted: field4.int({
|
|
2364
|
-
default: 0,
|
|
2365
|
-
description: "Lessons completed"
|
|
2366
|
-
}),
|
|
2367
|
-
quizzesPassed: field4.int({ default: 0, description: "Quizzes passed" }),
|
|
2368
|
-
cardsReviewed: field4.int({ default: 0, description: "Cards reviewed" }),
|
|
2369
|
-
streakDays: field4.int({ default: 0, description: "Streak days in period" }),
|
|
2370
|
-
league: field4.string({ isOptional: true, description: "League tier" }),
|
|
2371
|
-
orgId: field4.string({
|
|
2372
|
-
isOptional: true,
|
|
2373
|
-
description: "Organization scope"
|
|
2374
|
-
}),
|
|
2375
|
-
createdAt: field4.createdAt(),
|
|
2376
|
-
updatedAt: field4.updatedAt(),
|
|
2377
|
-
learner: field4.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2378
|
-
onDelete: "Cascade"
|
|
2379
|
-
})
|
|
2380
|
-
},
|
|
2381
|
-
indexes: [
|
|
2382
|
-
index4.unique(["learnerId", "periodType", "periodKey"], {
|
|
2383
|
-
name: "leaderboard_entry_unique"
|
|
2384
|
-
}),
|
|
2385
|
-
index4.on(["periodType", "periodKey", "xp"]),
|
|
2386
|
-
index4.on(["orgId", "periodType", "periodKey", "xp"])
|
|
2387
|
-
],
|
|
2388
|
-
enums: [LeaderboardPeriodEnum]
|
|
2389
|
-
});
|
|
2390
|
-
var HeartEntity = defineEntity4({
|
|
2391
|
-
name: "Heart",
|
|
2392
|
-
description: "Lives/hearts system for quiz attempts.",
|
|
2393
|
-
schema: "lssm_learning",
|
|
2394
|
-
map: "heart",
|
|
2395
|
-
fields: {
|
|
2396
|
-
id: field4.id({ description: "Unique heart record identifier" }),
|
|
2397
|
-
learnerId: field4.foreignKey({ description: "Learner" }),
|
|
2398
|
-
current: field4.int({ default: 5, description: "Current hearts" }),
|
|
2399
|
-
max: field4.int({ default: 5, description: "Maximum hearts" }),
|
|
2400
|
-
lastRefillAt: field4.dateTime({
|
|
2401
|
-
isOptional: true,
|
|
2402
|
-
description: "Last refill time"
|
|
2403
|
-
}),
|
|
2404
|
-
nextRefillAt: field4.dateTime({
|
|
2405
|
-
isOptional: true,
|
|
2406
|
-
description: "Next refill time"
|
|
2407
|
-
}),
|
|
2408
|
-
refillIntervalMinutes: field4.int({
|
|
2409
|
-
default: 240,
|
|
2410
|
-
description: "Minutes between refills"
|
|
2411
|
-
}),
|
|
2412
|
-
infiniteUntil: field4.dateTime({
|
|
2413
|
-
isOptional: true,
|
|
2414
|
-
description: "Infinite hearts until"
|
|
2415
|
-
}),
|
|
2416
|
-
createdAt: field4.createdAt(),
|
|
2417
|
-
updatedAt: field4.updatedAt(),
|
|
2418
|
-
learner: field4.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2419
|
-
onDelete: "Cascade"
|
|
2420
|
-
})
|
|
2421
|
-
},
|
|
2422
|
-
indexes: [
|
|
2423
|
-
index4.unique(["learnerId"], { name: "heart_learner_unique" }),
|
|
2424
|
-
index4.on(["nextRefillAt"])
|
|
2425
|
-
]
|
|
2426
|
-
});
|
|
2427
|
-
var XPTransactionEntity = defineEntity4({
|
|
2428
|
-
name: "XPTransaction",
|
|
2429
|
-
description: "Record of XP earned or spent.",
|
|
2430
|
-
schema: "lssm_learning",
|
|
2431
|
-
map: "xp_transaction",
|
|
2432
|
-
fields: {
|
|
2433
|
-
id: field4.id({ description: "Unique transaction identifier" }),
|
|
2434
|
-
learnerId: field4.foreignKey({ description: "Learner" }),
|
|
2435
|
-
amount: field4.int({
|
|
2436
|
-
description: "XP amount (positive = earned, negative = spent)"
|
|
2437
|
-
}),
|
|
2438
|
-
type: field4.string({
|
|
2439
|
-
description: "Transaction type (lesson, quiz, streak, achievement, etc.)"
|
|
2440
|
-
}),
|
|
2441
|
-
sourceType: field4.string({
|
|
2442
|
-
isOptional: true,
|
|
2443
|
-
description: "Source entity type"
|
|
2444
|
-
}),
|
|
2445
|
-
sourceId: field4.string({
|
|
2446
|
-
isOptional: true,
|
|
2447
|
-
description: "Source entity ID"
|
|
2448
|
-
}),
|
|
2449
|
-
description: field4.string({
|
|
2450
|
-
isOptional: true,
|
|
2451
|
-
description: "Human-readable description"
|
|
2452
|
-
}),
|
|
2453
|
-
balanceAfter: field4.int({ description: "Total XP after transaction" }),
|
|
2454
|
-
createdAt: field4.createdAt(),
|
|
2455
|
-
learner: field4.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2456
|
-
onDelete: "Cascade"
|
|
2457
|
-
})
|
|
2458
|
-
},
|
|
2459
|
-
indexes: [
|
|
2460
|
-
index4.on(["learnerId", "createdAt"]),
|
|
2461
|
-
index4.on(["type"]),
|
|
2462
|
-
index4.on(["sourceType", "sourceId"])
|
|
2463
|
-
]
|
|
2464
|
-
});
|
|
2465
|
-
var gamificationEntities = [
|
|
2466
|
-
AchievementEntity,
|
|
2467
|
-
LearnerAchievementEntity,
|
|
2468
|
-
StreakEntity,
|
|
2469
|
-
DailyGoalEntity,
|
|
2470
|
-
LeaderboardEntryEntity,
|
|
2471
|
-
HeartEntity,
|
|
2472
|
-
XPTransactionEntity
|
|
2473
|
-
];
|
|
2474
|
-
var gamificationEnums = [AchievementTypeEnum, LeaderboardPeriodEnum];
|
|
2475
|
-
|
|
2476
|
-
// src/entities/learner.ts
|
|
2477
|
-
import {
|
|
2478
|
-
defineEntity as defineEntity5,
|
|
2479
|
-
defineEntityEnum as defineEntityEnum5,
|
|
2480
|
-
field as field5,
|
|
2481
|
-
index as index5
|
|
2482
|
-
} from "@contractspec/lib.schema";
|
|
2483
|
-
var EnrollmentStatusEnum = defineEntityEnum5({
|
|
2484
|
-
name: "EnrollmentStatus",
|
|
2485
|
-
values: [
|
|
2486
|
-
"ENROLLED",
|
|
2487
|
-
"IN_PROGRESS",
|
|
2488
|
-
"COMPLETED",
|
|
2489
|
-
"DROPPED",
|
|
2490
|
-
"EXPIRED"
|
|
2491
|
-
],
|
|
2492
|
-
schema: "lssm_learning",
|
|
2493
|
-
description: "Status of a course enrollment."
|
|
2494
|
-
});
|
|
2495
|
-
var ProgressStatusEnum = defineEntityEnum5({
|
|
2496
|
-
name: "ProgressStatus",
|
|
2497
|
-
values: ["NOT_STARTED", "IN_PROGRESS", "COMPLETED", "SKIPPED"],
|
|
2498
|
-
schema: "lssm_learning",
|
|
2499
|
-
description: "Status of lesson progress."
|
|
2500
|
-
});
|
|
2501
|
-
var LearnerEntity = defineEntity5({
|
|
2502
|
-
name: "Learner",
|
|
2503
|
-
description: "A learner profile.",
|
|
2504
|
-
schema: "lssm_learning",
|
|
2505
|
-
map: "learner",
|
|
2506
|
-
fields: {
|
|
2507
|
-
id: field5.id({ description: "Unique learner identifier" }),
|
|
2508
|
-
userId: field5.string({ isUnique: true, description: "Associated user ID" }),
|
|
2509
|
-
displayName: field5.string({
|
|
2510
|
-
isOptional: true,
|
|
2511
|
-
description: "Display name"
|
|
2512
|
-
}),
|
|
2513
|
-
avatarUrl: field5.string({ isOptional: true, description: "Avatar URL" }),
|
|
2514
|
-
bio: field5.string({ isOptional: true, description: "Short bio" }),
|
|
2515
|
-
level: field5.int({ default: 1, description: "Current level" }),
|
|
2516
|
-
totalXp: field5.int({ default: 0, description: "Total XP earned" }),
|
|
2517
|
-
currentStreak: field5.int({
|
|
2518
|
-
default: 0,
|
|
2519
|
-
description: "Current streak days"
|
|
2520
|
-
}),
|
|
2521
|
-
longestStreak: field5.int({
|
|
2522
|
-
default: 0,
|
|
2523
|
-
description: "Longest streak ever"
|
|
2524
|
-
}),
|
|
2525
|
-
lastActivityAt: field5.dateTime({
|
|
2526
|
-
isOptional: true,
|
|
2527
|
-
description: "Last learning activity"
|
|
2528
|
-
}),
|
|
2529
|
-
locale: field5.string({
|
|
2530
|
-
isOptional: true,
|
|
2531
|
-
description: 'Preferred locale for learning content (e.g. "en", "fr", "es")'
|
|
2532
|
-
}),
|
|
2533
|
-
timezone: field5.string({
|
|
2534
|
-
default: '"UTC"',
|
|
2535
|
-
description: "Learner timezone"
|
|
2536
|
-
}),
|
|
2537
|
-
dailyGoalXp: field5.int({ default: 50, description: "Daily XP goal" }),
|
|
2538
|
-
reminderEnabled: field5.boolean({
|
|
2539
|
-
default: true,
|
|
2540
|
-
description: "Enable reminders"
|
|
2541
|
-
}),
|
|
2542
|
-
reminderTime: field5.string({
|
|
2543
|
-
isOptional: true,
|
|
2544
|
-
description: "Preferred reminder time"
|
|
2545
|
-
}),
|
|
2546
|
-
orgId: field5.string({
|
|
2547
|
-
isOptional: true,
|
|
2548
|
-
description: "Organization scope"
|
|
2549
|
-
}),
|
|
2550
|
-
metadata: field5.json({
|
|
2551
|
-
isOptional: true,
|
|
2552
|
-
description: "Additional metadata"
|
|
2553
|
-
}),
|
|
2554
|
-
createdAt: field5.createdAt(),
|
|
2555
|
-
updatedAt: field5.updatedAt(),
|
|
2556
|
-
enrollments: field5.hasMany("Enrollment"),
|
|
2557
|
-
lessonProgress: field5.hasMany("LessonProgress"),
|
|
2558
|
-
achievements: field5.hasMany("LearnerAchievement"),
|
|
2559
|
-
decks: field5.hasMany("Deck"),
|
|
2560
|
-
profile: field5.hasOne("LearnerProfile")
|
|
2561
|
-
},
|
|
2562
|
-
indexes: [
|
|
2563
|
-
index5.on(["orgId"]),
|
|
2564
|
-
index5.on(["totalXp"]),
|
|
2565
|
-
index5.on(["level"]),
|
|
2566
|
-
index5.on(["currentStreak"])
|
|
2567
|
-
]
|
|
2568
|
-
});
|
|
2569
|
-
var EnrollmentEntity = defineEntity5({
|
|
2570
|
-
name: "Enrollment",
|
|
2571
|
-
description: "A learner enrollment in a course.",
|
|
2572
|
-
schema: "lssm_learning",
|
|
2573
|
-
map: "enrollment",
|
|
2574
|
-
fields: {
|
|
2575
|
-
id: field5.id({ description: "Unique enrollment identifier" }),
|
|
2576
|
-
learnerId: field5.foreignKey({ description: "Enrolled learner" }),
|
|
2577
|
-
courseId: field5.foreignKey({ description: "Enrolled course" }),
|
|
2578
|
-
status: field5.enum("EnrollmentStatus", {
|
|
2579
|
-
default: "ENROLLED",
|
|
2580
|
-
description: "Enrollment status"
|
|
2581
|
-
}),
|
|
2582
|
-
progress: field5.int({
|
|
2583
|
-
default: 0,
|
|
2584
|
-
description: "Completion percentage (0-100)"
|
|
2585
|
-
}),
|
|
2586
|
-
completedLessons: field5.int({
|
|
2587
|
-
default: 0,
|
|
2588
|
-
description: "Number of completed lessons"
|
|
2589
|
-
}),
|
|
2590
|
-
totalLessons: field5.int({
|
|
2591
|
-
default: 0,
|
|
2592
|
-
description: "Total lessons in course"
|
|
2593
|
-
}),
|
|
2594
|
-
xpEarned: field5.int({
|
|
2595
|
-
default: 0,
|
|
2596
|
-
description: "XP earned in this course"
|
|
2597
|
-
}),
|
|
2598
|
-
startedAt: field5.dateTime({
|
|
2599
|
-
isOptional: true,
|
|
2600
|
-
description: "When learner started"
|
|
2601
|
-
}),
|
|
2602
|
-
completedAt: field5.dateTime({
|
|
2603
|
-
isOptional: true,
|
|
2604
|
-
description: "When learner completed"
|
|
2605
|
-
}),
|
|
2606
|
-
lastAccessedAt: field5.dateTime({
|
|
2607
|
-
isOptional: true,
|
|
2608
|
-
description: "Last access time"
|
|
2609
|
-
}),
|
|
2610
|
-
certificateId: field5.string({
|
|
2611
|
-
isOptional: true,
|
|
2612
|
-
description: "Issued certificate ID"
|
|
2613
|
-
}),
|
|
2614
|
-
metadata: field5.json({
|
|
2615
|
-
isOptional: true,
|
|
2616
|
-
description: "Additional metadata"
|
|
2617
|
-
}),
|
|
2618
|
-
createdAt: field5.createdAt(),
|
|
2619
|
-
updatedAt: field5.updatedAt(),
|
|
2620
|
-
learner: field5.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2621
|
-
onDelete: "Cascade"
|
|
2622
|
-
}),
|
|
2623
|
-
course: field5.belongsTo("Course", ["courseId"], ["id"], {
|
|
2624
|
-
onDelete: "Cascade"
|
|
2625
|
-
})
|
|
2626
|
-
},
|
|
2627
|
-
indexes: [
|
|
2628
|
-
index5.unique(["learnerId", "courseId"], { name: "enrollment_unique" }),
|
|
2629
|
-
index5.on(["learnerId", "status"]),
|
|
2630
|
-
index5.on(["courseId", "status"])
|
|
2631
|
-
],
|
|
2632
|
-
enums: [EnrollmentStatusEnum]
|
|
2633
|
-
});
|
|
2634
|
-
var LessonProgressEntity = defineEntity5({
|
|
2635
|
-
name: "LessonProgress",
|
|
2636
|
-
description: "Progress tracking for a lesson.",
|
|
2637
|
-
schema: "lssm_learning",
|
|
2638
|
-
map: "lesson_progress",
|
|
2639
|
-
fields: {
|
|
2640
|
-
id: field5.id({ description: "Unique progress identifier" }),
|
|
2641
|
-
learnerId: field5.foreignKey({ description: "Learner" }),
|
|
2642
|
-
lessonId: field5.foreignKey({ description: "Lesson" }),
|
|
2643
|
-
status: field5.enum("ProgressStatus", {
|
|
2644
|
-
default: "NOT_STARTED",
|
|
2645
|
-
description: "Progress status"
|
|
2646
|
-
}),
|
|
2647
|
-
progress: field5.int({
|
|
2648
|
-
default: 0,
|
|
2649
|
-
description: "Completion percentage (0-100)"
|
|
2650
|
-
}),
|
|
2651
|
-
score: field5.int({
|
|
2652
|
-
isOptional: true,
|
|
2653
|
-
description: "Score achieved (for quizzes)"
|
|
2654
|
-
}),
|
|
2655
|
-
attempts: field5.int({ default: 0, description: "Number of attempts" }),
|
|
2656
|
-
bestScore: field5.int({
|
|
2657
|
-
isOptional: true,
|
|
2658
|
-
description: "Best score across attempts"
|
|
2659
|
-
}),
|
|
2660
|
-
timeSpent: field5.int({ default: 0, description: "Time spent in seconds" }),
|
|
2661
|
-
xpEarned: field5.int({
|
|
2662
|
-
default: 0,
|
|
2663
|
-
description: "XP earned from this lesson"
|
|
2664
|
-
}),
|
|
2665
|
-
startedAt: field5.dateTime({
|
|
2666
|
-
isOptional: true,
|
|
2667
|
-
description: "When started"
|
|
2668
|
-
}),
|
|
2669
|
-
completedAt: field5.dateTime({
|
|
2670
|
-
isOptional: true,
|
|
2671
|
-
description: "When completed"
|
|
2672
|
-
}),
|
|
2673
|
-
lastAccessedAt: field5.dateTime({
|
|
2674
|
-
isOptional: true,
|
|
2675
|
-
description: "Last access time"
|
|
2676
|
-
}),
|
|
2677
|
-
bookmarks: field5.json({
|
|
2678
|
-
isOptional: true,
|
|
2679
|
-
description: "Content bookmarks"
|
|
2680
|
-
}),
|
|
2681
|
-
notes: field5.string({ isOptional: true, description: "Learner notes" }),
|
|
2682
|
-
createdAt: field5.createdAt(),
|
|
2683
|
-
updatedAt: field5.updatedAt(),
|
|
2684
|
-
learner: field5.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2685
|
-
onDelete: "Cascade"
|
|
2686
|
-
}),
|
|
2687
|
-
lesson: field5.belongsTo("Lesson", ["lessonId"], ["id"], {
|
|
2688
|
-
onDelete: "Cascade"
|
|
2689
|
-
})
|
|
2690
|
-
},
|
|
2691
|
-
indexes: [
|
|
2692
|
-
index5.unique(["learnerId", "lessonId"], { name: "lesson_progress_unique" }),
|
|
2693
|
-
index5.on(["learnerId", "status"]),
|
|
2694
|
-
index5.on(["lessonId"])
|
|
2695
|
-
],
|
|
2696
|
-
enums: [ProgressStatusEnum]
|
|
2697
|
-
});
|
|
2698
|
-
var ModuleCompletionEntity = defineEntity5({
|
|
2699
|
-
name: "ModuleCompletion",
|
|
2700
|
-
description: "Module completion record.",
|
|
2701
|
-
schema: "lssm_learning",
|
|
2702
|
-
map: "module_completion",
|
|
2703
|
-
fields: {
|
|
2704
|
-
id: field5.id({ description: "Unique completion identifier" }),
|
|
2705
|
-
learnerId: field5.foreignKey({ description: "Learner" }),
|
|
2706
|
-
moduleId: field5.foreignKey({ description: "Module" }),
|
|
2707
|
-
score: field5.int({ isOptional: true, description: "Average score" }),
|
|
2708
|
-
xpEarned: field5.int({ default: 0, description: "XP earned" }),
|
|
2709
|
-
timeSpent: field5.int({ default: 0, description: "Time spent in seconds" }),
|
|
2710
|
-
completedAt: field5.dateTime({ description: "When completed" }),
|
|
2711
|
-
createdAt: field5.createdAt(),
|
|
2712
|
-
learner: field5.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2713
|
-
onDelete: "Cascade"
|
|
2714
|
-
}),
|
|
2715
|
-
module: field5.belongsTo("CourseModule", ["moduleId"], ["id"], {
|
|
2716
|
-
onDelete: "Cascade"
|
|
2717
|
-
})
|
|
2718
|
-
},
|
|
2719
|
-
indexes: [
|
|
2720
|
-
index5.unique(["learnerId", "moduleId"], {
|
|
2721
|
-
name: "module_completion_unique"
|
|
2722
|
-
}),
|
|
2723
|
-
index5.on(["learnerId", "completedAt"])
|
|
2724
|
-
]
|
|
2725
|
-
});
|
|
2726
|
-
var CertificateEntity = defineEntity5({
|
|
2727
|
-
name: "Certificate",
|
|
2728
|
-
description: "Course completion certificate.",
|
|
2729
|
-
schema: "lssm_learning",
|
|
2730
|
-
map: "certificate",
|
|
2731
|
-
fields: {
|
|
2732
|
-
id: field5.id({ description: "Unique certificate identifier" }),
|
|
2733
|
-
learnerId: field5.foreignKey({ description: "Certificate holder" }),
|
|
2734
|
-
courseId: field5.foreignKey({ description: "Completed course" }),
|
|
2735
|
-
enrollmentId: field5.foreignKey({ description: "Associated enrollment" }),
|
|
2736
|
-
certificateNumber: field5.string({
|
|
2737
|
-
isUnique: true,
|
|
2738
|
-
description: "Unique certificate number"
|
|
2739
|
-
}),
|
|
2740
|
-
title: field5.string({ description: "Certificate title" }),
|
|
2741
|
-
description: field5.string({
|
|
2742
|
-
isOptional: true,
|
|
2743
|
-
description: "Certificate description"
|
|
2744
|
-
}),
|
|
2745
|
-
score: field5.int({ isOptional: true, description: "Final score" }),
|
|
2746
|
-
grade: field5.string({ isOptional: true, description: "Grade awarded" }),
|
|
2747
|
-
issuedAt: field5.dateTime({ description: "When issued" }),
|
|
2748
|
-
validUntil: field5.dateTime({
|
|
2749
|
-
isOptional: true,
|
|
2750
|
-
description: "Expiration date"
|
|
2751
|
-
}),
|
|
2752
|
-
verificationUrl: field5.string({
|
|
2753
|
-
isOptional: true,
|
|
2754
|
-
description: "Verification URL"
|
|
2755
|
-
}),
|
|
2756
|
-
credentialHash: field5.string({
|
|
2757
|
-
isOptional: true,
|
|
2758
|
-
description: "Credential hash for verification"
|
|
2759
|
-
}),
|
|
2760
|
-
isRevoked: field5.boolean({
|
|
2761
|
-
default: false,
|
|
2762
|
-
description: "Whether certificate is revoked"
|
|
2763
|
-
}),
|
|
2764
|
-
revokedAt: field5.dateTime({
|
|
2765
|
-
isOptional: true,
|
|
2766
|
-
description: "When revoked"
|
|
2767
|
-
}),
|
|
2768
|
-
revokedReason: field5.string({
|
|
2769
|
-
isOptional: true,
|
|
2770
|
-
description: "Revocation reason"
|
|
2771
|
-
}),
|
|
2772
|
-
metadata: field5.json({
|
|
2773
|
-
isOptional: true,
|
|
2774
|
-
description: "Additional metadata"
|
|
2775
|
-
}),
|
|
2776
|
-
createdAt: field5.createdAt(),
|
|
2777
|
-
learner: field5.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
2778
|
-
onDelete: "Cascade"
|
|
2779
|
-
}),
|
|
2780
|
-
course: field5.belongsTo("Course", ["courseId"], ["id"], {
|
|
2781
|
-
onDelete: "Cascade"
|
|
2782
|
-
})
|
|
2783
|
-
},
|
|
2784
|
-
indexes: [
|
|
2785
|
-
index5.on(["learnerId"]),
|
|
2786
|
-
index5.on(["courseId"]),
|
|
2787
|
-
index5.on(["issuedAt"])
|
|
2788
|
-
]
|
|
2789
|
-
});
|
|
2790
|
-
var learnerEntities = [
|
|
2791
|
-
LearnerEntity,
|
|
2792
|
-
EnrollmentEntity,
|
|
2793
|
-
LessonProgressEntity,
|
|
2794
|
-
ModuleCompletionEntity,
|
|
2795
|
-
CertificateEntity
|
|
2796
|
-
];
|
|
2797
|
-
var learnerEnums = [EnrollmentStatusEnum, ProgressStatusEnum];
|
|
2798
|
-
|
|
2799
|
-
// src/entities/onboarding.ts
|
|
2800
|
-
import {
|
|
2801
|
-
defineEntity as defineEntity6,
|
|
2802
|
-
defineEntityEnum as defineEntityEnum6,
|
|
2803
|
-
field as field6,
|
|
2804
|
-
index as index6
|
|
2805
|
-
} from "@contractspec/lib.schema";
|
|
2806
|
-
var OnboardingStepStatusEnum = defineEntityEnum6({
|
|
2807
|
-
name: "OnboardingStepStatus",
|
|
2808
|
-
values: ["PENDING", "IN_PROGRESS", "COMPLETED", "SKIPPED"],
|
|
2809
|
-
schema: "lssm_learning",
|
|
2810
|
-
description: "Status of an onboarding step."
|
|
2811
|
-
});
|
|
2812
|
-
var OnboardingTrackEntity = defineEntity6({
|
|
2813
|
-
name: "OnboardingTrack",
|
|
2814
|
-
description: "An onboarding track for a product.",
|
|
2815
|
-
schema: "lssm_learning",
|
|
2816
|
-
map: "onboarding_track",
|
|
2817
|
-
fields: {
|
|
2818
|
-
id: field6.id({ description: "Unique track identifier" }),
|
|
2819
|
-
productId: field6.string({ description: "Product this track is for" }),
|
|
2820
|
-
name: field6.string({ description: "Track name" }),
|
|
2821
|
-
description: field6.string({
|
|
2822
|
-
isOptional: true,
|
|
2823
|
-
description: "Track description"
|
|
2824
|
-
}),
|
|
2825
|
-
targetUserSegment: field6.string({
|
|
2826
|
-
isOptional: true,
|
|
2827
|
-
description: "Target user segment"
|
|
2828
|
-
}),
|
|
2829
|
-
targetRole: field6.string({
|
|
2830
|
-
isOptional: true,
|
|
2831
|
-
description: "Target user role"
|
|
2832
|
-
}),
|
|
2833
|
-
welcomeTitle: field6.string({
|
|
2834
|
-
isOptional: true,
|
|
2835
|
-
description: "Welcome message title"
|
|
2836
|
-
}),
|
|
2837
|
-
welcomeMessage: field6.string({
|
|
2838
|
-
isOptional: true,
|
|
2839
|
-
description: "Welcome message"
|
|
2840
|
-
}),
|
|
2841
|
-
completionTitle: field6.string({
|
|
2842
|
-
isOptional: true,
|
|
2843
|
-
description: "Completion message title"
|
|
2844
|
-
}),
|
|
2845
|
-
completionMessage: field6.string({
|
|
2846
|
-
isOptional: true,
|
|
2847
|
-
description: "Completion message"
|
|
2848
|
-
}),
|
|
2849
|
-
isActive: field6.boolean({
|
|
2850
|
-
default: true,
|
|
2851
|
-
description: "Whether track is active"
|
|
2852
|
-
}),
|
|
2853
|
-
isRequired: field6.boolean({
|
|
2854
|
-
default: false,
|
|
2855
|
-
description: "Whether track is required"
|
|
2856
|
-
}),
|
|
2857
|
-
canSkip: field6.boolean({
|
|
2858
|
-
default: true,
|
|
2859
|
-
description: "Whether steps can be skipped"
|
|
2860
|
-
}),
|
|
2861
|
-
totalXp: field6.int({
|
|
2862
|
-
default: 100,
|
|
2863
|
-
description: "Total XP for completing track"
|
|
2864
|
-
}),
|
|
2865
|
-
completionXpBonus: field6.int({
|
|
2866
|
-
isOptional: true,
|
|
2867
|
-
description: "Bonus XP for completing track"
|
|
2868
|
-
}),
|
|
2869
|
-
completionBadgeKey: field6.string({
|
|
2870
|
-
isOptional: true,
|
|
2871
|
-
description: "Badge awarded on completion"
|
|
2872
|
-
}),
|
|
2873
|
-
streakHoursWindow: field6.int({
|
|
2874
|
-
isOptional: true,
|
|
2875
|
-
description: "Hours window to finish for streak bonus"
|
|
2876
|
-
}),
|
|
2877
|
-
streakBonusXp: field6.int({
|
|
2878
|
-
isOptional: true,
|
|
2879
|
-
description: "Bonus XP if completed within streak window"
|
|
2880
|
-
}),
|
|
2881
|
-
orgId: field6.string({
|
|
2882
|
-
isOptional: true,
|
|
2883
|
-
description: "Organization scope"
|
|
2884
|
-
}),
|
|
2885
|
-
metadata: field6.json({
|
|
2886
|
-
isOptional: true,
|
|
2887
|
-
description: "Additional metadata"
|
|
2888
|
-
}),
|
|
2889
|
-
createdAt: field6.createdAt(),
|
|
2890
|
-
updatedAt: field6.updatedAt(),
|
|
2891
|
-
steps: field6.hasMany("OnboardingStep"),
|
|
2892
|
-
progress: field6.hasMany("OnboardingProgress")
|
|
2893
|
-
},
|
|
2894
|
-
indexes: [
|
|
2895
|
-
index6.on(["productId", "isActive"]),
|
|
2896
|
-
index6.on(["orgId"]),
|
|
2897
|
-
index6.unique(["productId", "targetUserSegment", "targetRole"], {
|
|
2898
|
-
name: "onboarding_track_target"
|
|
2899
|
-
})
|
|
2900
|
-
]
|
|
2901
|
-
});
|
|
2902
|
-
var OnboardingStepEntity = defineEntity6({
|
|
2903
|
-
name: "OnboardingStep",
|
|
2904
|
-
description: "A step in an onboarding track.",
|
|
2905
|
-
schema: "lssm_learning",
|
|
2906
|
-
map: "onboarding_step",
|
|
2907
|
-
fields: {
|
|
2908
|
-
id: field6.id({ description: "Unique step identifier" }),
|
|
2909
|
-
trackId: field6.foreignKey({ description: "Parent track" }),
|
|
2910
|
-
title: field6.string({ description: "Step title" }),
|
|
2911
|
-
description: field6.string({
|
|
2912
|
-
isOptional: true,
|
|
2913
|
-
description: "Step description"
|
|
2914
|
-
}),
|
|
2915
|
-
instructions: field6.string({
|
|
2916
|
-
isOptional: true,
|
|
2917
|
-
description: "How to complete the step"
|
|
2918
|
-
}),
|
|
2919
|
-
helpUrl: field6.string({
|
|
2920
|
-
isOptional: true,
|
|
2921
|
-
description: "Link to help documentation"
|
|
2922
|
-
}),
|
|
2923
|
-
order: field6.int({ default: 0, description: "Display order" }),
|
|
2924
|
-
triggerEvent: field6.string({
|
|
2925
|
-
isOptional: true,
|
|
2926
|
-
description: "Event that triggers step start"
|
|
2927
|
-
}),
|
|
2928
|
-
completionEvent: field6.string({
|
|
2929
|
-
description: "Event that completes the step"
|
|
2930
|
-
}),
|
|
2931
|
-
completionEventVersion: field6.int({
|
|
2932
|
-
isOptional: true,
|
|
2933
|
-
description: "Version of the completion event"
|
|
2934
|
-
}),
|
|
2935
|
-
completionSourceModule: field6.string({
|
|
2936
|
-
isOptional: true,
|
|
2937
|
-
description: "Module emitting the completion event"
|
|
2938
|
-
}),
|
|
2939
|
-
completionEventFilter: field6.json({
|
|
2940
|
-
isOptional: true,
|
|
2941
|
-
description: "Filter for completion event"
|
|
2942
|
-
}),
|
|
2943
|
-
actionUrl: field6.string({
|
|
2944
|
-
isOptional: true,
|
|
2945
|
-
description: "URL to navigate to complete"
|
|
2946
|
-
}),
|
|
2947
|
-
actionLabel: field6.string({
|
|
2948
|
-
isOptional: true,
|
|
2949
|
-
description: "Action button label"
|
|
2950
|
-
}),
|
|
2951
|
-
highlightSelector: field6.string({
|
|
2952
|
-
isOptional: true,
|
|
2953
|
-
description: "CSS selector to highlight"
|
|
2954
|
-
}),
|
|
2955
|
-
tooltipPosition: field6.string({
|
|
2956
|
-
isOptional: true,
|
|
2957
|
-
description: "Tooltip position"
|
|
2958
|
-
}),
|
|
2959
|
-
xpReward: field6.int({ default: 10, description: "XP for completing step" }),
|
|
2960
|
-
isRequired: field6.boolean({
|
|
2961
|
-
default: true,
|
|
2962
|
-
description: "Whether step is required"
|
|
2963
|
-
}),
|
|
2964
|
-
canSkip: field6.boolean({
|
|
2965
|
-
default: true,
|
|
2966
|
-
description: "Whether step can be skipped"
|
|
2967
|
-
}),
|
|
2968
|
-
metadata: field6.json({
|
|
2969
|
-
isOptional: true,
|
|
2970
|
-
description: "Additional metadata"
|
|
2971
|
-
}),
|
|
2972
|
-
createdAt: field6.createdAt(),
|
|
2973
|
-
updatedAt: field6.updatedAt(),
|
|
2974
|
-
track: field6.belongsTo("OnboardingTrack", ["trackId"], ["id"], {
|
|
2975
|
-
onDelete: "Cascade"
|
|
2976
|
-
})
|
|
2977
|
-
},
|
|
2978
|
-
indexes: [index6.on(["trackId", "order"]), index6.on(["completionEvent"])]
|
|
2979
|
-
});
|
|
2980
|
-
var OnboardingProgressEntity = defineEntity6({
|
|
2981
|
-
name: "OnboardingProgress",
|
|
2982
|
-
description: "Tracks user progress through an onboarding track.",
|
|
2983
|
-
schema: "lssm_learning",
|
|
2984
|
-
map: "onboarding_progress",
|
|
2985
|
-
fields: {
|
|
2986
|
-
id: field6.id({ description: "Unique progress identifier" }),
|
|
2987
|
-
learnerId: field6.foreignKey({ description: "Learner" }),
|
|
2988
|
-
trackId: field6.foreignKey({ description: "Onboarding track" }),
|
|
2989
|
-
currentStepId: field6.string({
|
|
2990
|
-
isOptional: true,
|
|
2991
|
-
description: "Current step ID"
|
|
2992
|
-
}),
|
|
2993
|
-
completedSteps: field6.json({
|
|
2994
|
-
default: "[]",
|
|
2995
|
-
description: "Array of completed step IDs"
|
|
2996
|
-
}),
|
|
2997
|
-
skippedSteps: field6.json({
|
|
2998
|
-
default: "[]",
|
|
2999
|
-
description: "Array of skipped step IDs"
|
|
3000
|
-
}),
|
|
3001
|
-
progress: field6.int({
|
|
3002
|
-
default: 0,
|
|
3003
|
-
description: "Completion percentage (0-100)"
|
|
3004
|
-
}),
|
|
3005
|
-
isCompleted: field6.boolean({
|
|
3006
|
-
default: false,
|
|
3007
|
-
description: "Whether track is completed"
|
|
3008
|
-
}),
|
|
3009
|
-
xpEarned: field6.int({ default: 0, description: "XP earned from track" }),
|
|
3010
|
-
startedAt: field6.dateTime({ description: "When user started" }),
|
|
3011
|
-
completedAt: field6.dateTime({
|
|
3012
|
-
isOptional: true,
|
|
3013
|
-
description: "When user completed"
|
|
3014
|
-
}),
|
|
3015
|
-
lastActivityAt: field6.dateTime({
|
|
3016
|
-
isOptional: true,
|
|
3017
|
-
description: "Last activity"
|
|
3018
|
-
}),
|
|
3019
|
-
isDismissed: field6.boolean({
|
|
3020
|
-
default: false,
|
|
3021
|
-
description: "Whether user dismissed onboarding"
|
|
3022
|
-
}),
|
|
3023
|
-
dismissedAt: field6.dateTime({
|
|
3024
|
-
isOptional: true,
|
|
3025
|
-
description: "When dismissed"
|
|
3026
|
-
}),
|
|
3027
|
-
metadata: field6.json({
|
|
3028
|
-
isOptional: true,
|
|
3029
|
-
description: "Additional metadata"
|
|
3030
|
-
}),
|
|
3031
|
-
createdAt: field6.createdAt(),
|
|
3032
|
-
updatedAt: field6.updatedAt(),
|
|
3033
|
-
learner: field6.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
3034
|
-
onDelete: "Cascade"
|
|
3035
|
-
}),
|
|
3036
|
-
track: field6.belongsTo("OnboardingTrack", ["trackId"], ["id"], {
|
|
3037
|
-
onDelete: "Cascade"
|
|
3038
|
-
})
|
|
3039
|
-
},
|
|
3040
|
-
indexes: [
|
|
3041
|
-
index6.unique(["learnerId", "trackId"], {
|
|
3042
|
-
name: "onboarding_progress_unique"
|
|
3043
|
-
}),
|
|
3044
|
-
index6.on(["learnerId", "isCompleted"]),
|
|
3045
|
-
index6.on(["trackId"])
|
|
3046
|
-
],
|
|
3047
|
-
enums: [OnboardingStepStatusEnum]
|
|
3048
|
-
});
|
|
3049
|
-
var OnboardingStepCompletionEntity = defineEntity6({
|
|
3050
|
-
name: "OnboardingStepCompletion",
|
|
3051
|
-
description: "Individual step completion record.",
|
|
3052
|
-
schema: "lssm_learning",
|
|
3053
|
-
map: "onboarding_step_completion",
|
|
3054
|
-
fields: {
|
|
3055
|
-
id: field6.id({ description: "Unique completion identifier" }),
|
|
3056
|
-
progressId: field6.foreignKey({ description: "Parent progress record" }),
|
|
3057
|
-
stepId: field6.foreignKey({ description: "Completed step" }),
|
|
3058
|
-
status: field6.enum("OnboardingStepStatus", {
|
|
3059
|
-
description: "Completion status"
|
|
3060
|
-
}),
|
|
3061
|
-
xpEarned: field6.int({ default: 0, description: "XP earned" }),
|
|
3062
|
-
triggeringEvent: field6.string({
|
|
3063
|
-
isOptional: true,
|
|
3064
|
-
description: "Event that triggered completion"
|
|
3065
|
-
}),
|
|
3066
|
-
eventPayload: field6.json({
|
|
3067
|
-
isOptional: true,
|
|
3068
|
-
description: "Event payload"
|
|
3069
|
-
}),
|
|
3070
|
-
completedAt: field6.dateTime({ description: "When completed" }),
|
|
3071
|
-
createdAt: field6.createdAt(),
|
|
3072
|
-
progress: field6.belongsTo("OnboardingProgress", ["progressId"], ["id"], {
|
|
3073
|
-
onDelete: "Cascade"
|
|
3074
|
-
}),
|
|
3075
|
-
step: field6.belongsTo("OnboardingStep", ["stepId"], ["id"], {
|
|
3076
|
-
onDelete: "Cascade"
|
|
3077
|
-
})
|
|
3078
|
-
},
|
|
3079
|
-
indexes: [
|
|
3080
|
-
index6.unique(["progressId", "stepId"], { name: "step_completion_unique" }),
|
|
3081
|
-
index6.on(["completedAt"])
|
|
3082
|
-
]
|
|
3083
|
-
});
|
|
3084
|
-
var onboardingEntities = [
|
|
3085
|
-
OnboardingTrackEntity,
|
|
3086
|
-
OnboardingStepEntity,
|
|
3087
|
-
OnboardingProgressEntity,
|
|
3088
|
-
OnboardingStepCompletionEntity
|
|
3089
|
-
];
|
|
3090
|
-
var onboardingEnums = [OnboardingStepStatusEnum];
|
|
3091
|
-
|
|
3092
|
-
// src/entities/quiz.ts
|
|
3093
|
-
import {
|
|
3094
|
-
defineEntity as defineEntity7,
|
|
3095
|
-
defineEntityEnum as defineEntityEnum7,
|
|
3096
|
-
field as field7,
|
|
3097
|
-
index as index7
|
|
3098
|
-
} from "@contractspec/lib.schema";
|
|
3099
|
-
var QuestionTypeEnum = defineEntityEnum7({
|
|
3100
|
-
name: "QuestionType",
|
|
3101
|
-
values: [
|
|
3102
|
-
"MULTIPLE_CHOICE",
|
|
3103
|
-
"TRUE_FALSE",
|
|
3104
|
-
"FILL_BLANK",
|
|
3105
|
-
"MATCHING",
|
|
3106
|
-
"SHORT_ANSWER",
|
|
3107
|
-
"CODE"
|
|
3108
|
-
],
|
|
3109
|
-
schema: "lssm_learning",
|
|
3110
|
-
description: "Type of quiz question."
|
|
3111
|
-
});
|
|
3112
|
-
var QuizStatusEnum = defineEntityEnum7({
|
|
3113
|
-
name: "QuizStatus",
|
|
3114
|
-
values: ["DRAFT", "PUBLISHED", "ARCHIVED"],
|
|
3115
|
-
schema: "lssm_learning",
|
|
3116
|
-
description: "Publication status of a quiz."
|
|
3117
|
-
});
|
|
3118
|
-
var AttemptStatusEnum = defineEntityEnum7({
|
|
3119
|
-
name: "AttemptStatus",
|
|
3120
|
-
values: ["IN_PROGRESS", "COMPLETED", "TIMED_OUT", "ABANDONED"],
|
|
3121
|
-
schema: "lssm_learning",
|
|
3122
|
-
description: "Status of a quiz attempt."
|
|
3123
|
-
});
|
|
3124
|
-
var QuizEntity = defineEntity7({
|
|
3125
|
-
name: "Quiz",
|
|
3126
|
-
description: "A quiz assessment.",
|
|
3127
|
-
schema: "lssm_learning",
|
|
3128
|
-
map: "quiz",
|
|
3129
|
-
fields: {
|
|
3130
|
-
id: field7.id({ description: "Unique quiz identifier" }),
|
|
3131
|
-
lessonId: field7.foreignKey({
|
|
3132
|
-
isOptional: true,
|
|
3133
|
-
description: "Associated lesson"
|
|
3134
|
-
}),
|
|
3135
|
-
title: field7.string({ description: "Quiz title" }),
|
|
3136
|
-
description: field7.string({
|
|
3137
|
-
isOptional: true,
|
|
3138
|
-
description: "Quiz description"
|
|
3139
|
-
}),
|
|
3140
|
-
instructions: field7.string({
|
|
3141
|
-
isOptional: true,
|
|
3142
|
-
description: "Quiz instructions"
|
|
3143
|
-
}),
|
|
3144
|
-
passingScore: field7.int({
|
|
3145
|
-
default: 70,
|
|
3146
|
-
description: "Passing score percentage"
|
|
3147
|
-
}),
|
|
3148
|
-
timeLimit: field7.int({
|
|
3149
|
-
isOptional: true,
|
|
3150
|
-
description: "Time limit in seconds"
|
|
3151
|
-
}),
|
|
3152
|
-
maxAttempts: field7.int({
|
|
3153
|
-
isOptional: true,
|
|
3154
|
-
description: "Maximum attempts allowed"
|
|
3155
|
-
}),
|
|
3156
|
-
shuffleQuestions: field7.boolean({
|
|
3157
|
-
default: false,
|
|
3158
|
-
description: "Shuffle question order"
|
|
3159
|
-
}),
|
|
3160
|
-
shuffleOptions: field7.boolean({
|
|
3161
|
-
default: false,
|
|
3162
|
-
description: "Shuffle answer options"
|
|
3163
|
-
}),
|
|
3164
|
-
showCorrectAnswers: field7.boolean({
|
|
3165
|
-
default: true,
|
|
3166
|
-
description: "Show correct answers after"
|
|
3167
|
-
}),
|
|
3168
|
-
showExplanations: field7.boolean({
|
|
3169
|
-
default: true,
|
|
3170
|
-
description: "Show explanations after"
|
|
3171
|
-
}),
|
|
3172
|
-
status: field7.enum("QuizStatus", {
|
|
3173
|
-
default: "DRAFT",
|
|
3174
|
-
description: "Publication status"
|
|
3175
|
-
}),
|
|
3176
|
-
totalPoints: field7.int({
|
|
3177
|
-
default: 0,
|
|
3178
|
-
description: "Total points available"
|
|
3179
|
-
}),
|
|
3180
|
-
xpReward: field7.int({ default: 20, description: "XP for passing" }),
|
|
3181
|
-
orgId: field7.string({
|
|
3182
|
-
isOptional: true,
|
|
3183
|
-
description: "Organization scope"
|
|
3184
|
-
}),
|
|
3185
|
-
metadata: field7.json({
|
|
3186
|
-
isOptional: true,
|
|
3187
|
-
description: "Additional metadata"
|
|
3188
|
-
}),
|
|
3189
|
-
createdAt: field7.createdAt(),
|
|
3190
|
-
updatedAt: field7.updatedAt(),
|
|
3191
|
-
lesson: field7.belongsTo("Lesson", ["lessonId"], ["id"], {
|
|
3192
|
-
onDelete: "Cascade"
|
|
3193
|
-
}),
|
|
3194
|
-
questions: field7.hasMany("Question"),
|
|
3195
|
-
attempts: field7.hasMany("QuizAttempt")
|
|
3196
|
-
},
|
|
3197
|
-
indexes: [index7.on(["lessonId"]), index7.on(["status"]), index7.on(["orgId"])],
|
|
3198
|
-
enums: [QuizStatusEnum]
|
|
3199
|
-
});
|
|
3200
|
-
var QuestionEntity = defineEntity7({
|
|
3201
|
-
name: "Question",
|
|
3202
|
-
description: "A quiz question.",
|
|
3203
|
-
schema: "lssm_learning",
|
|
3204
|
-
map: "question",
|
|
3205
|
-
fields: {
|
|
3206
|
-
id: field7.id({ description: "Unique question identifier" }),
|
|
3207
|
-
quizId: field7.foreignKey({ description: "Parent quiz" }),
|
|
3208
|
-
type: field7.enum("QuestionType", { description: "Question type" }),
|
|
3209
|
-
content: field7.string({ description: "Question text" }),
|
|
3210
|
-
mediaUrl: field7.string({ isOptional: true, description: "Question media" }),
|
|
3211
|
-
points: field7.int({ default: 1, description: "Points for correct answer" }),
|
|
3212
|
-
codeLanguage: field7.string({
|
|
3213
|
-
isOptional: true,
|
|
3214
|
-
description: "Programming language"
|
|
3215
|
-
}),
|
|
3216
|
-
codeTemplate: field7.string({
|
|
3217
|
-
isOptional: true,
|
|
3218
|
-
description: "Starter code template"
|
|
3219
|
-
}),
|
|
3220
|
-
testCases: field7.json({
|
|
3221
|
-
isOptional: true,
|
|
3222
|
-
description: "Test cases for code validation"
|
|
3223
|
-
}),
|
|
3224
|
-
explanation: field7.string({
|
|
3225
|
-
isOptional: true,
|
|
3226
|
-
description: "Explanation of correct answer"
|
|
3227
|
-
}),
|
|
3228
|
-
hint: field7.string({
|
|
3229
|
-
isOptional: true,
|
|
3230
|
-
description: "Hint for the question"
|
|
3231
|
-
}),
|
|
3232
|
-
order: field7.int({ default: 0, description: "Display order" }),
|
|
3233
|
-
skillId: field7.string({
|
|
3234
|
-
isOptional: true,
|
|
3235
|
-
description: "Associated skill"
|
|
3236
|
-
}),
|
|
3237
|
-
metadata: field7.json({
|
|
3238
|
-
isOptional: true,
|
|
3239
|
-
description: "Additional metadata"
|
|
3240
|
-
}),
|
|
3241
|
-
createdAt: field7.createdAt(),
|
|
3242
|
-
updatedAt: field7.updatedAt(),
|
|
3243
|
-
quiz: field7.belongsTo("Quiz", ["quizId"], ["id"], { onDelete: "Cascade" }),
|
|
3244
|
-
options: field7.hasMany("QuestionOption")
|
|
3245
|
-
},
|
|
3246
|
-
indexes: [
|
|
3247
|
-
index7.on(["quizId", "order"]),
|
|
3248
|
-
index7.on(["type"]),
|
|
3249
|
-
index7.on(["skillId"])
|
|
3250
|
-
],
|
|
3251
|
-
enums: [QuestionTypeEnum]
|
|
3252
|
-
});
|
|
3253
|
-
var QuestionOptionEntity = defineEntity7({
|
|
3254
|
-
name: "QuestionOption",
|
|
3255
|
-
description: "An answer option for a question.",
|
|
3256
|
-
schema: "lssm_learning",
|
|
3257
|
-
map: "question_option",
|
|
3258
|
-
fields: {
|
|
3259
|
-
id: field7.id({ description: "Unique option identifier" }),
|
|
3260
|
-
questionId: field7.foreignKey({ description: "Parent question" }),
|
|
3261
|
-
content: field7.string({ description: "Option text" }),
|
|
3262
|
-
matchContent: field7.string({
|
|
3263
|
-
isOptional: true,
|
|
3264
|
-
description: "Match pair content"
|
|
3265
|
-
}),
|
|
3266
|
-
isCorrect: field7.boolean({
|
|
3267
|
-
default: false,
|
|
3268
|
-
description: "Whether option is correct"
|
|
3269
|
-
}),
|
|
3270
|
-
feedback: field7.string({
|
|
3271
|
-
isOptional: true,
|
|
3272
|
-
description: "Feedback when selected"
|
|
3273
|
-
}),
|
|
3274
|
-
order: field7.int({ default: 0, description: "Display order" }),
|
|
3275
|
-
metadata: field7.json({
|
|
3276
|
-
isOptional: true,
|
|
3277
|
-
description: "Additional metadata"
|
|
3278
|
-
}),
|
|
3279
|
-
createdAt: field7.createdAt(),
|
|
3280
|
-
updatedAt: field7.updatedAt(),
|
|
3281
|
-
question: field7.belongsTo("Question", ["questionId"], ["id"], {
|
|
3282
|
-
onDelete: "Cascade"
|
|
3283
|
-
})
|
|
3284
|
-
},
|
|
3285
|
-
indexes: [index7.on(["questionId", "order"])]
|
|
3286
|
-
});
|
|
3287
|
-
var QuizAttemptEntity = defineEntity7({
|
|
3288
|
-
name: "QuizAttempt",
|
|
3289
|
-
description: "A learner quiz attempt.",
|
|
3290
|
-
schema: "lssm_learning",
|
|
3291
|
-
map: "quiz_attempt",
|
|
3292
|
-
fields: {
|
|
3293
|
-
id: field7.id({ description: "Unique attempt identifier" }),
|
|
3294
|
-
learnerId: field7.foreignKey({ description: "Learner" }),
|
|
3295
|
-
quizId: field7.foreignKey({ description: "Quiz" }),
|
|
3296
|
-
status: field7.enum("AttemptStatus", {
|
|
3297
|
-
default: "IN_PROGRESS",
|
|
3298
|
-
description: "Attempt status"
|
|
3299
|
-
}),
|
|
3300
|
-
score: field7.int({ isOptional: true, description: "Score achieved" }),
|
|
3301
|
-
percentageScore: field7.int({
|
|
3302
|
-
isOptional: true,
|
|
3303
|
-
description: "Percentage score"
|
|
3304
|
-
}),
|
|
3305
|
-
passed: field7.boolean({ isOptional: true, description: "Whether passed" }),
|
|
3306
|
-
totalQuestions: field7.int({ default: 0, description: "Total questions" }),
|
|
3307
|
-
answeredQuestions: field7.int({
|
|
3308
|
-
default: 0,
|
|
3309
|
-
description: "Questions answered"
|
|
3310
|
-
}),
|
|
3311
|
-
correctAnswers: field7.int({ default: 0, description: "Correct answers" }),
|
|
3312
|
-
answers: field7.json({ isOptional: true, description: "Submitted answers" }),
|
|
3313
|
-
xpEarned: field7.int({ default: 0, description: "XP earned" }),
|
|
3314
|
-
timeSpent: field7.int({ default: 0, description: "Time spent in seconds" }),
|
|
3315
|
-
startedAt: field7.dateTime({ description: "When started" }),
|
|
3316
|
-
completedAt: field7.dateTime({
|
|
3317
|
-
isOptional: true,
|
|
3318
|
-
description: "When completed"
|
|
3319
|
-
}),
|
|
3320
|
-
attemptNumber: field7.int({
|
|
3321
|
-
default: 1,
|
|
3322
|
-
description: "Which attempt this is"
|
|
3323
|
-
}),
|
|
3324
|
-
metadata: field7.json({
|
|
3325
|
-
isOptional: true,
|
|
3326
|
-
description: "Additional metadata"
|
|
3327
|
-
}),
|
|
3328
|
-
createdAt: field7.createdAt(),
|
|
3329
|
-
updatedAt: field7.updatedAt(),
|
|
3330
|
-
learner: field7.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
3331
|
-
onDelete: "Cascade"
|
|
3332
|
-
}),
|
|
3333
|
-
quiz: field7.belongsTo("Quiz", ["quizId"], ["id"], { onDelete: "Cascade" })
|
|
3334
|
-
},
|
|
3335
|
-
indexes: [
|
|
3336
|
-
index7.on(["learnerId", "quizId"]),
|
|
3337
|
-
index7.on(["learnerId", "status"]),
|
|
3338
|
-
index7.on(["quizId", "status"])
|
|
3339
|
-
],
|
|
3340
|
-
enums: [AttemptStatusEnum]
|
|
3341
|
-
});
|
|
3342
|
-
var SkillAssessmentEntity = defineEntity7({
|
|
3343
|
-
name: "SkillAssessment",
|
|
3344
|
-
description: "Assessment of a skill based on quiz performance.",
|
|
3345
|
-
schema: "lssm_learning",
|
|
3346
|
-
map: "skill_assessment",
|
|
3347
|
-
fields: {
|
|
3348
|
-
id: field7.id({ description: "Unique assessment identifier" }),
|
|
3349
|
-
learnerId: field7.foreignKey({ description: "Learner" }),
|
|
3350
|
-
skillId: field7.string({ description: "Skill identifier" }),
|
|
3351
|
-
skillName: field7.string({ description: "Skill name" }),
|
|
3352
|
-
level: field7.int({ default: 1, description: "Proficiency level (1-5)" }),
|
|
3353
|
-
score: field7.int({ default: 0, description: "Assessment score (0-100)" }),
|
|
3354
|
-
confidence: field7.decimal({
|
|
3355
|
-
default: 0.5,
|
|
3356
|
-
description: "Confidence in assessment"
|
|
3357
|
-
}),
|
|
3358
|
-
questionsAnswered: field7.int({
|
|
3359
|
-
default: 0,
|
|
3360
|
-
description: "Total questions answered"
|
|
3361
|
-
}),
|
|
3362
|
-
questionsCorrect: field7.int({ default: 0, description: "Total correct" }),
|
|
3363
|
-
assessedAt: field7.dateTime({ description: "Last assessment time" }),
|
|
3364
|
-
createdAt: field7.createdAt(),
|
|
3365
|
-
updatedAt: field7.updatedAt(),
|
|
3366
|
-
learner: field7.belongsTo("Learner", ["learnerId"], ["id"], {
|
|
3367
|
-
onDelete: "Cascade"
|
|
3368
|
-
})
|
|
3369
|
-
},
|
|
3370
|
-
indexes: [
|
|
3371
|
-
index7.unique(["learnerId", "skillId"], { name: "skill_assessment_unique" }),
|
|
3372
|
-
index7.on(["learnerId", "level"]),
|
|
3373
|
-
index7.on(["skillId"])
|
|
3374
|
-
]
|
|
3375
|
-
});
|
|
3376
|
-
var quizEntities = [
|
|
3377
|
-
QuizEntity,
|
|
3378
|
-
QuestionEntity,
|
|
3379
|
-
QuestionOptionEntity,
|
|
3380
|
-
QuizAttemptEntity,
|
|
3381
|
-
SkillAssessmentEntity
|
|
3382
|
-
];
|
|
3383
|
-
var quizEnums = [QuestionTypeEnum, QuizStatusEnum, AttemptStatusEnum];
|
|
3384
|
-
// src/entities/index.ts
|
|
3385
|
-
var learningJourneyEntities = [
|
|
3386
|
-
...courseEntities,
|
|
3387
|
-
...learnerEntities,
|
|
3388
|
-
...onboardingEntities,
|
|
3389
|
-
...flashcardEntities,
|
|
3390
|
-
...quizEntities,
|
|
3391
|
-
...gamificationEntities,
|
|
3392
|
-
...aiEntities
|
|
3393
|
-
];
|
|
3394
|
-
var learningJourneyEnums = [
|
|
3395
|
-
...courseEnums,
|
|
3396
|
-
...learnerEnums,
|
|
3397
|
-
...onboardingEnums,
|
|
3398
|
-
...flashcardEnums,
|
|
3399
|
-
...quizEnums,
|
|
3400
|
-
...gamificationEnums,
|
|
3401
|
-
...aiEnums
|
|
3402
|
-
];
|
|
3403
|
-
var learningJourneySchemaContribution = {
|
|
3404
|
-
moduleId: "@contractspec/module.learning-journey",
|
|
3405
|
-
entities: learningJourneyEntities,
|
|
3406
|
-
enums: learningJourneyEnums
|
|
3407
|
-
};
|
|
3408
|
-
|
|
3409
|
-
// src/events.ts
|
|
3410
|
-
import { defineEvent } from "@contractspec/lib.contracts-spec";
|
|
3411
|
-
import { defineSchemaModel as defineSchemaModel3, ScalarTypeEnum as ScalarTypeEnum3 } from "@contractspec/lib.schema";
|
|
3412
|
-
var CoursePublishedPayload = defineSchemaModel3({
|
|
3413
|
-
name: "CoursePublishedEventPayload",
|
|
3414
|
-
description: "Payload when a course is published",
|
|
3415
|
-
fields: {
|
|
3416
|
-
courseId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3417
|
-
title: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3418
|
-
authorId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3419
|
-
publishedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3420
|
-
}
|
|
3421
|
-
});
|
|
3422
|
-
var CoursePublishedEvent = defineEvent({
|
|
3423
|
-
meta: {
|
|
3424
|
-
key: "course.published",
|
|
3425
|
-
version: "1.0.0",
|
|
3426
|
-
description: "A course has been published.",
|
|
3427
|
-
stability: "stable",
|
|
3428
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3429
|
-
tags: ["learning", "course"]
|
|
3430
|
-
},
|
|
3431
|
-
payload: CoursePublishedPayload
|
|
3432
|
-
});
|
|
3433
|
-
var EnrollmentCreatedPayload = defineSchemaModel3({
|
|
3434
|
-
name: "EnrollmentCreatedEventPayload",
|
|
3435
|
-
description: "Payload when a learner enrolls in a course",
|
|
3436
|
-
fields: {
|
|
3437
|
-
enrollmentId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3438
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3439
|
-
courseId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3440
|
-
enrolledAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3441
|
-
}
|
|
3442
|
-
});
|
|
3443
|
-
var EnrollmentCreatedEvent = defineEvent({
|
|
3444
|
-
meta: {
|
|
3445
|
-
key: "enrollment.created",
|
|
3446
|
-
version: "1.0.0",
|
|
3447
|
-
description: "A learner has enrolled in a course.",
|
|
3448
|
-
stability: "stable",
|
|
3449
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3450
|
-
tags: ["learning", "enrollment"]
|
|
3451
|
-
},
|
|
3452
|
-
payload: EnrollmentCreatedPayload
|
|
3453
|
-
});
|
|
3454
|
-
var LessonCompletedPayload = defineSchemaModel3({
|
|
3455
|
-
name: "LessonCompletedEventPayload",
|
|
3456
|
-
description: "Payload when a lesson is completed",
|
|
3457
|
-
fields: {
|
|
3458
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3459
|
-
lessonId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3460
|
-
courseId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3461
|
-
score: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: true },
|
|
3462
|
-
xpEarned: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3463
|
-
timeSpent: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3464
|
-
completedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3465
|
-
}
|
|
3466
|
-
});
|
|
3467
|
-
var LessonCompletedEvent = defineEvent({
|
|
3468
|
-
meta: {
|
|
3469
|
-
key: "lesson.completed",
|
|
3470
|
-
version: "1.0.0",
|
|
3471
|
-
description: "A learner has completed a lesson.",
|
|
3472
|
-
stability: "stable",
|
|
3473
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3474
|
-
tags: ["learning", "progress", "lesson"]
|
|
3475
|
-
},
|
|
3476
|
-
payload: LessonCompletedPayload
|
|
3477
|
-
});
|
|
3478
|
-
var CourseCompletedPayload = defineSchemaModel3({
|
|
3479
|
-
name: "CourseCompletedEventPayload",
|
|
3480
|
-
description: "Payload when a course is completed",
|
|
3481
|
-
fields: {
|
|
3482
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3483
|
-
courseId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3484
|
-
enrollmentId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3485
|
-
score: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: true },
|
|
3486
|
-
xpEarned: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3487
|
-
certificateId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
|
|
3488
|
-
completedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3489
|
-
}
|
|
3490
|
-
});
|
|
3491
|
-
var CourseCompletedEvent = defineEvent({
|
|
3492
|
-
meta: {
|
|
3493
|
-
key: "course.completed",
|
|
3494
|
-
version: "1.0.0",
|
|
3495
|
-
description: "A learner has completed a course.",
|
|
3496
|
-
stability: "stable",
|
|
3497
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3498
|
-
tags: ["learning", "progress", "course"]
|
|
3499
|
-
},
|
|
3500
|
-
payload: CourseCompletedPayload
|
|
3501
|
-
});
|
|
3502
|
-
var OnboardingStartedPayload = defineSchemaModel3({
|
|
3503
|
-
name: "OnboardingStartedEventPayload",
|
|
3504
|
-
description: "Payload when onboarding starts",
|
|
3505
|
-
fields: {
|
|
3506
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3507
|
-
trackId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3508
|
-
productId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3509
|
-
startedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3510
|
-
}
|
|
3511
|
-
});
|
|
3512
|
-
var OnboardingStartedEvent = defineEvent({
|
|
3513
|
-
meta: {
|
|
3514
|
-
key: "onboarding.started",
|
|
3515
|
-
version: "1.0.0",
|
|
3516
|
-
description: "A learner has started onboarding.",
|
|
3517
|
-
stability: "stable",
|
|
3518
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3519
|
-
tags: ["learning", "onboarding"]
|
|
3520
|
-
},
|
|
3521
|
-
payload: OnboardingStartedPayload
|
|
3522
|
-
});
|
|
3523
|
-
var OnboardingStepCompletedPayload = defineSchemaModel3({
|
|
3524
|
-
name: "OnboardingStepCompletedEventPayload",
|
|
3525
|
-
description: "Payload when an onboarding step is completed",
|
|
3526
|
-
fields: {
|
|
3527
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3528
|
-
trackId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3529
|
-
stepId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3530
|
-
triggeringEvent: {
|
|
3531
|
-
type: ScalarTypeEnum3.String_unsecure(),
|
|
3532
|
-
isOptional: true
|
|
3533
|
-
},
|
|
3534
|
-
xpEarned: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3535
|
-
completedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3536
|
-
}
|
|
3537
|
-
});
|
|
3538
|
-
var OnboardingStepCompletedEvent = defineEvent({
|
|
3539
|
-
meta: {
|
|
3540
|
-
key: "onboarding.step_completed",
|
|
3541
|
-
version: "1.0.0",
|
|
3542
|
-
description: "An onboarding step has been completed.",
|
|
3543
|
-
stability: "stable",
|
|
3544
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3545
|
-
tags: ["learning", "onboarding"]
|
|
3546
|
-
},
|
|
3547
|
-
payload: OnboardingStepCompletedPayload
|
|
3548
|
-
});
|
|
3549
|
-
var OnboardingCompletedPayload = defineSchemaModel3({
|
|
3550
|
-
name: "OnboardingCompletedEventPayload",
|
|
3551
|
-
description: "Payload when onboarding is completed",
|
|
3552
|
-
fields: {
|
|
3553
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3554
|
-
trackId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3555
|
-
productId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3556
|
-
xpEarned: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3557
|
-
completedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3558
|
-
}
|
|
3559
|
-
});
|
|
3560
|
-
var OnboardingCompletedEvent = defineEvent({
|
|
3561
|
-
meta: {
|
|
3562
|
-
key: "onboarding.completed",
|
|
3563
|
-
version: "1.0.0",
|
|
3564
|
-
description: "A learner has completed onboarding.",
|
|
3565
|
-
stability: "stable",
|
|
3566
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3567
|
-
tags: ["learning", "onboarding"]
|
|
3568
|
-
},
|
|
3569
|
-
payload: OnboardingCompletedPayload
|
|
3570
|
-
});
|
|
3571
|
-
var CardReviewedPayload = defineSchemaModel3({
|
|
3572
|
-
name: "CardReviewedEventPayload",
|
|
3573
|
-
description: "Payload when a flashcard is reviewed",
|
|
3574
|
-
fields: {
|
|
3575
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3576
|
-
cardId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3577
|
-
deckId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3578
|
-
rating: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3579
|
-
responseTimeMs: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: true },
|
|
3580
|
-
reviewedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3581
|
-
}
|
|
3582
|
-
});
|
|
3583
|
-
var CardReviewedEvent = defineEvent({
|
|
3584
|
-
meta: {
|
|
3585
|
-
key: "flashcard.reviewed",
|
|
3586
|
-
version: "1.0.0",
|
|
3587
|
-
description: "A flashcard has been reviewed.",
|
|
3588
|
-
stability: "stable",
|
|
3589
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3590
|
-
tags: ["learning", "flashcards"]
|
|
3591
|
-
},
|
|
3592
|
-
payload: CardReviewedPayload
|
|
3593
|
-
});
|
|
3594
|
-
var QuizStartedPayload = defineSchemaModel3({
|
|
3595
|
-
name: "QuizStartedEventPayload",
|
|
3596
|
-
description: "Payload when a quiz is started",
|
|
3597
|
-
fields: {
|
|
3598
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3599
|
-
quizId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3600
|
-
attemptId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3601
|
-
attemptNumber: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3602
|
-
startedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3603
|
-
}
|
|
3604
|
-
});
|
|
3605
|
-
var QuizStartedEvent = defineEvent({
|
|
3606
|
-
meta: {
|
|
3607
|
-
key: "quiz.started",
|
|
3608
|
-
version: "1.0.0",
|
|
3609
|
-
description: "A quiz attempt has started.",
|
|
3610
|
-
stability: "stable",
|
|
3611
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3612
|
-
tags: ["learning", "quiz"]
|
|
3613
|
-
},
|
|
3614
|
-
payload: QuizStartedPayload
|
|
3615
|
-
});
|
|
3616
|
-
var QuizCompletedPayload = defineSchemaModel3({
|
|
3617
|
-
name: "QuizCompletedEventPayload",
|
|
3618
|
-
description: "Payload when a quiz is completed",
|
|
3619
|
-
fields: {
|
|
3620
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3621
|
-
quizId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3622
|
-
attemptId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3623
|
-
score: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3624
|
-
percentageScore: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3625
|
-
passed: { type: ScalarTypeEnum3.Boolean(), isOptional: false },
|
|
3626
|
-
xpEarned: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3627
|
-
timeSpent: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3628
|
-
completedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3629
|
-
}
|
|
3630
|
-
});
|
|
3631
|
-
var QuizCompletedEvent = defineEvent({
|
|
3632
|
-
meta: {
|
|
3633
|
-
key: "quiz.completed",
|
|
3634
|
-
version: "1.0.0",
|
|
3635
|
-
description: "A quiz attempt has been completed.",
|
|
3636
|
-
stability: "stable",
|
|
3637
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3638
|
-
tags: ["learning", "quiz"]
|
|
3639
|
-
},
|
|
3640
|
-
payload: QuizCompletedPayload
|
|
3641
|
-
});
|
|
3642
|
-
var XpEarnedPayload = defineSchemaModel3({
|
|
3643
|
-
name: "XpEarnedEventPayload",
|
|
3644
|
-
description: "Payload when XP is earned",
|
|
3645
|
-
fields: {
|
|
3646
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3647
|
-
amount: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3648
|
-
source: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3649
|
-
sourceId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
|
|
3650
|
-
totalXp: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3651
|
-
earnedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3652
|
-
}
|
|
3653
|
-
});
|
|
3654
|
-
var XpEarnedEvent = defineEvent({
|
|
3655
|
-
meta: {
|
|
3656
|
-
key: "xp.earned",
|
|
3657
|
-
version: "1.0.0",
|
|
3658
|
-
description: "XP has been earned.",
|
|
3659
|
-
stability: "stable",
|
|
3660
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3661
|
-
tags: ["learning", "gamification", "xp"]
|
|
3662
|
-
},
|
|
3663
|
-
payload: XpEarnedPayload
|
|
3664
|
-
});
|
|
3665
|
-
var LevelUpPayload = defineSchemaModel3({
|
|
3666
|
-
name: "LevelUpEventPayload",
|
|
3667
|
-
description: "Payload when a learner levels up",
|
|
3668
|
-
fields: {
|
|
3669
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3670
|
-
previousLevel: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3671
|
-
newLevel: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3672
|
-
totalXp: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3673
|
-
leveledUpAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3674
|
-
}
|
|
3675
|
-
});
|
|
3676
|
-
var LevelUpEvent = defineEvent({
|
|
3677
|
-
meta: {
|
|
3678
|
-
key: "level.up",
|
|
3679
|
-
version: "1.0.0",
|
|
3680
|
-
description: "A learner has leveled up.",
|
|
3681
|
-
stability: "stable",
|
|
3682
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3683
|
-
tags: ["learning", "gamification", "level"]
|
|
3684
|
-
},
|
|
3685
|
-
payload: LevelUpPayload
|
|
3686
|
-
});
|
|
3687
|
-
var StreakUpdatedPayload = defineSchemaModel3({
|
|
3688
|
-
name: "StreakUpdatedEventPayload",
|
|
3689
|
-
description: "Payload when a streak is updated",
|
|
3690
|
-
fields: {
|
|
3691
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3692
|
-
previousStreak: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3693
|
-
currentStreak: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3694
|
-
longestStreak: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3695
|
-
freezeUsed: { type: ScalarTypeEnum3.Boolean(), isOptional: false },
|
|
3696
|
-
updatedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3697
|
-
}
|
|
3698
|
-
});
|
|
3699
|
-
var StreakUpdatedEvent = defineEvent({
|
|
3700
|
-
meta: {
|
|
3701
|
-
key: "streak.updated",
|
|
3702
|
-
version: "1.0.0",
|
|
3703
|
-
description: "A streak has been updated.",
|
|
3704
|
-
stability: "stable",
|
|
3705
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3706
|
-
tags: ["learning", "gamification", "streak"]
|
|
3707
|
-
},
|
|
3708
|
-
payload: StreakUpdatedPayload
|
|
3709
|
-
});
|
|
3710
|
-
var AchievementUnlockedPayload = defineSchemaModel3({
|
|
3711
|
-
name: "AchievementUnlockedEventPayload",
|
|
3712
|
-
description: "Payload when an achievement is unlocked",
|
|
3713
|
-
fields: {
|
|
3714
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3715
|
-
achievementId: {
|
|
3716
|
-
type: ScalarTypeEnum3.String_unsecure(),
|
|
3717
|
-
isOptional: false
|
|
3718
|
-
},
|
|
3719
|
-
achievementKey: {
|
|
3720
|
-
type: ScalarTypeEnum3.String_unsecure(),
|
|
3721
|
-
isOptional: false
|
|
3722
|
-
},
|
|
3723
|
-
achievementName: {
|
|
3724
|
-
type: ScalarTypeEnum3.String_unsecure(),
|
|
3725
|
-
isOptional: false
|
|
3726
|
-
},
|
|
3727
|
-
xpEarned: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3728
|
-
unlockedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3729
|
-
}
|
|
3730
|
-
});
|
|
3731
|
-
var AchievementUnlockedEvent = defineEvent({
|
|
3732
|
-
meta: {
|
|
3733
|
-
key: "achievement.unlocked",
|
|
3734
|
-
version: "1.0.0",
|
|
3735
|
-
description: "An achievement has been unlocked.",
|
|
3736
|
-
stability: "stable",
|
|
3737
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3738
|
-
tags: ["learning", "gamification", "achievement"]
|
|
3739
|
-
},
|
|
3740
|
-
payload: AchievementUnlockedPayload
|
|
3741
|
-
});
|
|
3742
|
-
var DailyGoalCompletedPayload = defineSchemaModel3({
|
|
3743
|
-
name: "DailyGoalCompletedEventPayload",
|
|
3744
|
-
description: "Payload when a daily goal is completed",
|
|
3745
|
-
fields: {
|
|
3746
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3747
|
-
date: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3748
|
-
targetXp: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3749
|
-
actualXp: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false },
|
|
3750
|
-
completedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3751
|
-
}
|
|
3752
|
-
});
|
|
3753
|
-
var DailyGoalCompletedEvent = defineEvent({
|
|
3754
|
-
meta: {
|
|
3755
|
-
key: "daily_goal.completed",
|
|
3756
|
-
version: "1.0.0",
|
|
3757
|
-
description: "A daily goal has been completed.",
|
|
3758
|
-
stability: "stable",
|
|
3759
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3760
|
-
tags: ["learning", "gamification", "goal"]
|
|
3761
|
-
},
|
|
3762
|
-
payload: DailyGoalCompletedPayload
|
|
3763
|
-
});
|
|
3764
|
-
var CertificateIssuedPayload = defineSchemaModel3({
|
|
3765
|
-
name: "CertificateIssuedEventPayload",
|
|
3766
|
-
description: "Payload when a certificate is issued",
|
|
3767
|
-
fields: {
|
|
3768
|
-
certificateId: {
|
|
3769
|
-
type: ScalarTypeEnum3.String_unsecure(),
|
|
3770
|
-
isOptional: false
|
|
3771
|
-
},
|
|
3772
|
-
learnerId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3773
|
-
courseId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
3774
|
-
certificateNumber: {
|
|
3775
|
-
type: ScalarTypeEnum3.String_unsecure(),
|
|
3776
|
-
isOptional: false
|
|
3777
|
-
},
|
|
3778
|
-
issuedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
3779
|
-
}
|
|
3780
|
-
});
|
|
3781
|
-
var CertificateIssuedEvent = defineEvent({
|
|
3782
|
-
meta: {
|
|
3783
|
-
key: "certificate.issued",
|
|
3784
|
-
version: "1.0.0",
|
|
3785
|
-
description: "A certificate has been issued.",
|
|
3786
|
-
stability: "stable",
|
|
3787
|
-
owners: [...LEARNING_JOURNEY_OWNERS],
|
|
3788
|
-
tags: ["learning", "certificate"]
|
|
3789
|
-
},
|
|
3790
|
-
payload: CertificateIssuedPayload
|
|
3791
|
-
});
|
|
3792
|
-
var LearningJourneyEvents = {
|
|
3793
|
-
CoursePublishedEvent,
|
|
3794
|
-
EnrollmentCreatedEvent,
|
|
3795
|
-
LessonCompletedEvent,
|
|
3796
|
-
CourseCompletedEvent,
|
|
3797
|
-
OnboardingStartedEvent,
|
|
3798
|
-
OnboardingStepCompletedEvent,
|
|
3799
|
-
OnboardingCompletedEvent,
|
|
3800
|
-
CardReviewedEvent,
|
|
3801
|
-
QuizStartedEvent,
|
|
3802
|
-
QuizCompletedEvent,
|
|
3803
|
-
XpEarnedEvent,
|
|
3804
|
-
LevelUpEvent,
|
|
3805
|
-
StreakUpdatedEvent,
|
|
3806
|
-
AchievementUnlockedEvent,
|
|
3807
|
-
DailyGoalCompletedEvent,
|
|
3808
|
-
CertificateIssuedEvent
|
|
3809
|
-
};
|
|
3810
|
-
|
|
3811
|
-
// src/learning-journey.feature.ts
|
|
3812
|
-
import { defineFeature } from "@contractspec/lib.contracts-spec";
|
|
3813
|
-
var LearningJourneyFeature = defineFeature({
|
|
3814
|
-
meta: {
|
|
3815
|
-
key: "learning-journey",
|
|
3816
|
-
version: "1.0.0",
|
|
3817
|
-
title: "Learning Journey",
|
|
3818
|
-
description: "Learning platform with courses, onboarding, flashcards, and gamification",
|
|
3819
|
-
domain: "learning",
|
|
3820
|
-
owners: ["@platform.learning-journey"],
|
|
3821
|
-
tags: ["learning", "onboarding", "courses", "flashcards", "gamification"],
|
|
3822
|
-
stability: "stable"
|
|
3823
|
-
},
|
|
3824
|
-
operations: [
|
|
3825
|
-
{ key: "learning.onboarding.recordEvent", version: "1.0.0" },
|
|
3826
|
-
{ key: "learning.onboarding.listTracks", version: "1.0.0" },
|
|
3827
|
-
{ key: "learning.onboarding.getProgress", version: "1.0.0" },
|
|
3828
|
-
{ key: "learning.enroll", version: "1.0.0" },
|
|
3829
|
-
{ key: "learning.completeLesson", version: "1.0.0" },
|
|
3830
|
-
{ key: "learning.submitCardReview", version: "1.0.0" },
|
|
3831
|
-
{ key: "learning.getDueCards", version: "1.0.0" },
|
|
3832
|
-
{ key: "learning.getDashboard", version: "1.0.0" }
|
|
3833
|
-
],
|
|
3834
|
-
events: [
|
|
3835
|
-
{ key: "course.published", version: "1.0.0" },
|
|
3836
|
-
{ key: "course.completed", version: "1.0.0" },
|
|
3837
|
-
{ key: "enrollment.created", version: "1.0.0" },
|
|
3838
|
-
{ key: "lesson.completed", version: "1.0.0" },
|
|
3839
|
-
{ key: "onboarding.started", version: "1.0.0" },
|
|
3840
|
-
{ key: "onboarding.step_completed", version: "1.0.0" },
|
|
3841
|
-
{ key: "onboarding.completed", version: "1.0.0" },
|
|
3842
|
-
{ key: "flashcard.reviewed", version: "1.0.0" },
|
|
3843
|
-
{ key: "quiz.started", version: "1.0.0" },
|
|
3844
|
-
{ key: "quiz.completed", version: "1.0.0" },
|
|
3845
|
-
{ key: "xp.earned", version: "1.0.0" },
|
|
3846
|
-
{ key: "level.up", version: "1.0.0" },
|
|
3847
|
-
{ key: "streak.updated", version: "1.0.0" },
|
|
3848
|
-
{ key: "achievement.unlocked", version: "1.0.0" },
|
|
3849
|
-
{ key: "daily_goal.completed", version: "1.0.0" },
|
|
3850
|
-
{ key: "certificate.issued", version: "1.0.0" }
|
|
3851
|
-
],
|
|
3852
|
-
presentations: [],
|
|
3853
|
-
opToPresentation: [],
|
|
3854
|
-
presentationsTargets: [],
|
|
3855
|
-
capabilities: {
|
|
3856
|
-
provides: [
|
|
3857
|
-
{ key: "learning-journey", version: "1.0.0" },
|
|
3858
|
-
{ key: "onboarding", version: "1.0.0" },
|
|
3859
|
-
{ key: "gamification", version: "1.0.0" }
|
|
3860
|
-
],
|
|
3861
|
-
requires: [{ key: "identity", version: "1.0.0" }]
|
|
3862
|
-
}
|
|
3863
|
-
});
|
|
3864
|
-
export {
|
|
3865
|
-
xpEngine,
|
|
3866
|
-
streakEngine,
|
|
3867
|
-
srsEngine,
|
|
3868
|
-
quizEnums,
|
|
3869
|
-
quizEntities,
|
|
3870
|
-
onboardingEnums,
|
|
3871
|
-
onboardingEntities,
|
|
3872
|
-
learningJourneySchemaContribution,
|
|
3873
|
-
learningJourneyEnums,
|
|
3874
|
-
learningJourneyEntities,
|
|
3875
|
-
learnerEnums,
|
|
3876
|
-
learnerEntities,
|
|
3877
|
-
getXpSourceLabel,
|
|
3878
|
-
gamificationEnums,
|
|
3879
|
-
gamificationEntities,
|
|
3880
|
-
flashcardEnums,
|
|
3881
|
-
flashcardEntities,
|
|
3882
|
-
courseEnums,
|
|
3883
|
-
courseEntities,
|
|
3884
|
-
aiEnums,
|
|
3885
|
-
aiEntities,
|
|
3886
|
-
XpEarnedEvent,
|
|
3887
|
-
XPTransactionEntity,
|
|
3888
|
-
XPEngine,
|
|
3889
|
-
SuccessOutput,
|
|
3890
|
-
SubmitCardReviewInput,
|
|
3891
|
-
SubmitCardReviewContract,
|
|
3892
|
-
StreakUpdatedEvent,
|
|
3893
|
-
StreakEntity,
|
|
3894
|
-
StreakEngine,
|
|
3895
|
-
SkillMapEntity,
|
|
3896
|
-
SkillAssessmentEntity,
|
|
3897
|
-
SRSEngine,
|
|
3898
|
-
RecordOnboardingEventContract,
|
|
3899
|
-
RecommendationTypeEnum,
|
|
3900
|
-
RecommendationEntity,
|
|
3901
|
-
QuizStatusEnum,
|
|
3902
|
-
QuizStartedEvent,
|
|
3903
|
-
QuizEntity,
|
|
3904
|
-
QuizCompletedEvent,
|
|
3905
|
-
QuizAttemptEntity,
|
|
3906
|
-
QuestionTypeEnum,
|
|
3907
|
-
QuestionOptionEntity,
|
|
3908
|
-
QuestionEntity,
|
|
3909
|
-
ProgressStatusEnum,
|
|
3910
|
-
ProgressModel,
|
|
3911
|
-
OnboardingTrackModel,
|
|
3912
|
-
OnboardingTrackEntity,
|
|
3913
|
-
OnboardingStepStatusEnum,
|
|
3914
|
-
OnboardingStepProgressModel,
|
|
3915
|
-
OnboardingStepModel,
|
|
3916
|
-
OnboardingStepEntity,
|
|
3917
|
-
OnboardingStepCompletionEntity,
|
|
3918
|
-
OnboardingStepCompletedEvent,
|
|
3919
|
-
OnboardingStartedEvent,
|
|
3920
|
-
OnboardingProgressModel,
|
|
3921
|
-
OnboardingProgressEntity,
|
|
3922
|
-
OnboardingCompletedEvent,
|
|
3923
|
-
ModuleCompletionEntity,
|
|
3924
|
-
ListOnboardingTracksContract,
|
|
3925
|
-
LevelUpEvent,
|
|
3926
|
-
LessonTypeEnum,
|
|
3927
|
-
LessonProgressEntity,
|
|
3928
|
-
LessonEntity,
|
|
3929
|
-
LessonContentEntity,
|
|
3930
|
-
LessonCompletedEvent,
|
|
3931
|
-
LearningStyleEnum,
|
|
3932
|
-
LearningPathEntity,
|
|
3933
|
-
LearningJourneyFeature,
|
|
3934
|
-
LearningJourneyEvents,
|
|
3935
|
-
LearningGapEntity,
|
|
3936
|
-
LearnerProfileEntity,
|
|
3937
|
-
LearnerModel,
|
|
3938
|
-
LearnerEntity,
|
|
3939
|
-
LearnerDashboardModel,
|
|
3940
|
-
LearnerAchievementEntity,
|
|
3941
|
-
LeaderboardPeriodEnum,
|
|
3942
|
-
LeaderboardEntryEntity,
|
|
3943
|
-
LEARNING_JOURNEY_OWNERS,
|
|
3944
|
-
HeartEntity,
|
|
3945
|
-
GetOnboardingProgressContract,
|
|
3946
|
-
GetLearnerDashboardInput,
|
|
3947
|
-
GetLearnerDashboardContract,
|
|
3948
|
-
GetDueCardsOutput,
|
|
3949
|
-
GetDueCardsInput,
|
|
3950
|
-
GetDueCardsContract,
|
|
3951
|
-
EnrollmentStatusEnum,
|
|
3952
|
-
EnrollmentModel,
|
|
3953
|
-
EnrollmentEntity,
|
|
3954
|
-
EnrollmentCreatedEvent,
|
|
3955
|
-
EnrollInCourseInput,
|
|
3956
|
-
EnrollInCourseContract,
|
|
3957
|
-
DeckModel,
|
|
3958
|
-
DeckEntity,
|
|
3959
|
-
DailyGoalEntity,
|
|
3960
|
-
DailyGoalCompletedEvent,
|
|
3961
|
-
DEFAULT_XP_CONFIG,
|
|
3962
|
-
DEFAULT_STREAK_CONFIG,
|
|
3963
|
-
DEFAULT_SRS_CONFIG,
|
|
3964
|
-
CourseStatusEnum,
|
|
3965
|
-
CoursePublishedEvent,
|
|
3966
|
-
CourseModuleEntity,
|
|
3967
|
-
CourseModel,
|
|
3968
|
-
CourseEntity,
|
|
3969
|
-
CourseDifficultyEnum,
|
|
3970
|
-
CourseCompletedEvent,
|
|
3971
|
-
ContentTypeEnum,
|
|
3972
|
-
CompleteLessonInput,
|
|
3973
|
-
CompleteLessonContract,
|
|
3974
|
-
CertificateIssuedEvent,
|
|
3975
|
-
CertificateEntity,
|
|
3976
|
-
CardScheduleEntity,
|
|
3977
|
-
CardReviewedEvent,
|
|
3978
|
-
CardReviewEntity,
|
|
3979
|
-
CardRatingEnum,
|
|
3980
|
-
CardModel,
|
|
3981
|
-
CardEntity,
|
|
3982
|
-
AttemptStatusEnum,
|
|
3983
|
-
AchievementUnlockedEvent,
|
|
3984
|
-
AchievementTypeEnum,
|
|
3985
|
-
AchievementModel,
|
|
3986
|
-
AchievementEntity
|
|
3987
|
-
};
|
|
80
|
+
- Use Feature Flags to trial new tracks or reward rules; default safe/off.`}];ij(ej);var jZ={learningSteps:[1,10],graduatingInterval:1,easyInterval:4,relearningSteps:[10],minEaseFactor:1.3,maxInterval:365,intervalModifier:1,newIntervalModifier:0.5,hardIntervalModifier:1.2,easyBonus:1.3};class Zj{config;constructor(j={}){this.config={...jZ,...j}}calculateNextReview(j,q,W=new Date){if(!j.isGraduated&&!j.isRelearning)return this.handleLearningCard(j,q,W);if(j.isRelearning)return this.handleRelearningCard(j,q,W);return this.handleReviewCard(j,q,W)}getInitialState(){return{interval:0,easeFactor:2.5,repetitions:0,learningStep:0,isGraduated:!1,isRelearning:!1,lapses:0}}isDue(j,q=new Date){return j<=q}getOverdueDays(j,q=new Date){let W=q.getTime()-j.getTime();return Math.floor(W/86400000)}handleLearningCard(j,q,W){let I=this.config.learningSteps,Q=j.learningStep,Y=!1,F=0,k;switch(q){case"AGAIN":Q=0,F=I[0]??1,k=this.addMinutes(W,F);break;case"HARD":F=I[Q]??I[0]??1,k=this.addMinutes(W,F);break;case"GOOD":if(Q++,Q>=I.length)Y=!0,F=this.config.graduatingInterval,k=this.addDays(W,F);else F=I[Q]??10,k=this.addMinutes(W,F);break;case"EASY":Y=!0,F=this.config.easyInterval,k=this.addDays(W,F);break}return{interval:Y?F:0,easeFactor:j.easeFactor,repetitions:Y?1:0,nextReviewAt:k,learningStep:Q,isGraduated:Y,isRelearning:!1,lapses:j.lapses}}handleRelearningCard(j,q,W){let I=this.config.relearningSteps,Q=j.learningStep,Y=!0,F=0,k;switch(q){case"AGAIN":Q=0,F=I[0]??10,k=this.addMinutes(W,F);break;case"HARD":F=I[Q]??I[0]??10,k=this.addMinutes(W,F);break;case"GOOD":if(Q++,Q>=I.length)Y=!1,F=Math.max(1,Math.floor(j.interval*this.config.newIntervalModifier)),k=this.addDays(W,F);else F=I[Q]??10,k=this.addMinutes(W,F);break;case"EASY":Y=!1,F=Math.max(1,Math.floor(j.interval*this.config.newIntervalModifier*1.5)),k=this.addDays(W,F);break}return{interval:Y?j.interval:F,easeFactor:j.easeFactor,repetitions:Y?j.repetitions:j.repetitions+1,nextReviewAt:k,learningStep:Q,isGraduated:!0,isRelearning:Y,lapses:j.lapses}}handleReviewCard(j,q,W){let I,Q=j.easeFactor,Y=j.repetitions,F=!1,k=0,y=j.lapses;switch(q){case"AGAIN":return y++,F=!0,k=0,Q=Math.max(this.config.minEaseFactor,Q-0.2),I=j.interval,{interval:I,easeFactor:Q,repetitions:Y,nextReviewAt:this.addMinutes(W,this.config.relearningSteps[0]??10),learningStep:k,isGraduated:!0,isRelearning:!0,lapses:y};case"HARD":Q=Math.max(this.config.minEaseFactor,Q-0.15),I=Math.max(j.interval+1,j.interval*this.config.hardIntervalModifier);break;case"GOOD":I=j.interval*Q*this.config.intervalModifier,Y++;break;case"EASY":Q=Q+0.15,I=j.interval*Q*this.config.easyBonus*this.config.intervalModifier,Y++;break}return I=Math.min(Math.round(I),this.config.maxInterval),I=Math.max(1,I),{interval:I,easeFactor:Q,repetitions:Y,nextReviewAt:this.addDays(W,I),learningStep:k,isGraduated:!0,isRelearning:F,lapses:y}}addMinutes(j,q){return new Date(j.getTime()+q*60*1000)}addDays(j,q){return new Date(j.getTime()+q*24*60*60*1000)}}var j1=new Zj;var ZZ={timezone:"UTC",freezesPerMonth:2,maxFreezes:5,gracePeriodHours:4};class $j{config;constructor(j={}){this.config={...ZZ,...j}}update(j,q=new Date){let W=this.getDateString(q),I={state:{...j},streakMaintained:!1,streakLost:!1,freezeUsed:!1,newStreak:!1,daysMissed:0};if(!j.lastActivityDate)return I.state.currentStreak=1,I.state.longestStreak=Math.max(1,j.longestStreak),I.state.lastActivityAt=q,I.state.lastActivityDate=W,I.newStreak=!0,I.streakMaintained=!0,I;if(j.lastActivityDate===W)return I.state.lastActivityAt=q,I.streakMaintained=!0,I;let Q=this.getDaysBetween(j.lastActivityDate,W);if(Q===1)return I.state.currentStreak=j.currentStreak+1,I.state.longestStreak=Math.max(I.state.currentStreak,j.longestStreak),I.state.lastActivityAt=q,I.state.lastActivityDate=W,I.streakMaintained=!0,I;I.daysMissed=Q-1;let Y=I.daysMissed;if(Y<=j.freezesRemaining)return I.state.freezesRemaining=j.freezesRemaining-Y,I.state.freezeUsedAt=q,I.state.currentStreak=j.currentStreak+1,I.state.longestStreak=Math.max(I.state.currentStreak,j.longestStreak),I.state.lastActivityAt=q,I.state.lastActivityDate=W,I.freezeUsed=!0,I.streakMaintained=!0,I;return I.streakLost=!0,I.state.currentStreak=1,I.state.lastActivityAt=q,I.state.lastActivityDate=W,I.newStreak=!0,I}checkStatus(j,q=new Date){if(!j.lastActivityDate)return{isActive:!1,willExpireAt:null,canUseFreeze:!1,daysUntilExpiry:0};let W=this.getDateString(q),I=this.getDaysBetween(j.lastActivityDate,W);if(I===0){let Y=this.addDays(q,1);return Y.setHours(23,59,59,999),{isActive:!0,willExpireAt:Y,canUseFreeze:j.freezesRemaining>0,daysUntilExpiry:1}}if(I===1){let Y=new Date(q);return Y.setHours(23+this.config.gracePeriodHours,59,59,999),{isActive:!0,willExpireAt:Y,canUseFreeze:j.freezesRemaining>0,daysUntilExpiry:0}}let Q=I-1;return{isActive:Q<=j.freezesRemaining,willExpireAt:null,canUseFreeze:Q<=j.freezesRemaining,daysUntilExpiry:-Q}}useFreeze(j,q=new Date){if(j.freezesRemaining<=0)return null;return{...j,freezesRemaining:j.freezesRemaining-1,freezeUsedAt:q}}awardMonthlyFreezes(j){return{...j,freezesRemaining:Math.min(j.freezesRemaining+this.config.freezesPerMonth,this.config.maxFreezes)}}getInitialState(){return{currentStreak:0,longestStreak:0,lastActivityAt:null,lastActivityDate:null,freezesRemaining:this.config.freezesPerMonth,freezeUsedAt:null}}getMilestones(j){let q=[3,7,14,30,60,90,180,365,500,1000],W=q.filter((Q)=>j>=Q),I=q.find((Q)=>j<Q)??null;return{achieved:W,next:I}}getDateString(j){let q=j.getFullYear(),W=String(j.getMonth()+1).padStart(2,"0"),I=String(j.getDate()).padStart(2,"0");return`${q}-${W}-${I}`}getDaysBetween(j,q){let W=new Date(j),Q=new Date(q).getTime()-W.getTime();return Math.floor(Q/86400000)}addDays(j,q){return new Date(j.getTime()+q*24*60*60*1000)}}var $1=new $j;import{defineTranslation as $Z}from"@contractspec/lib.contracts-spec/translations";var Hj=$Z({meta:{key:"learning-journey.messages",version:"1.0.0",domain:"learning-journey",description:"XP source labels for the learning-journey module",owners:["platform"],stability:"experimental"},locale:"en",fallback:"en",messages:{"xp.source.base":{value:"Base",description:"XP breakdown label for base XP"},"xp.source.scoreBonus":{value:"Score Bonus",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Perfect Score",description:"XP breakdown label for perfect score bonus"},"xp.source.firstAttempt":{value:"First Attempt",description:"XP breakdown label for first attempt bonus"},"xp.source.retryPenalty":{value:"Retry Penalty",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Streak Bonus",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as HZ}from"@contractspec/lib.contracts-spec/translations";var Vj=HZ({meta:{key:"learning-journey.messages",version:"1.0.0",domain:"learning-journey",description:"XP source labels (Spanish)",owners:["platform"],stability:"experimental"},locale:"es",fallback:"en",messages:{"xp.source.base":{value:"Base",description:"XP breakdown label for base XP"},"xp.source.scoreBonus":{value:"Bonificación por puntuación",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Puntuación perfecta",description:"XP breakdown label for perfect score bonus"},"xp.source.firstAttempt":{value:"Primer intento",description:"XP breakdown label for first attempt bonus"},"xp.source.retryPenalty":{value:"Penalización por reintento",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonificación por racha",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as VZ}from"@contractspec/lib.contracts-spec/translations";var _j=VZ({meta:{key:"learning-journey.messages",version:"1.0.0",domain:"learning-journey",description:"XP source labels (French)",owners:["platform"],stability:"experimental"},locale:"fr",fallback:"en",messages:{"xp.source.base":{value:"Base",description:"XP breakdown label for base XP"},"xp.source.scoreBonus":{value:"Bonus de score",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Score parfait",description:"XP breakdown label for perfect score bonus"},"xp.source.firstAttempt":{value:"Premier essai",description:"XP breakdown label for first attempt bonus"},"xp.source.retryPenalty":{value:"Pénalité de réessai",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonus de série",description:"XP breakdown label for streak bonus"}}});import{createI18nFactory as _Z}from"@contractspec/lib.contracts-spec/translations";var c=_Z({specKey:"learning-journey.messages",catalogs:[Hj,_j,Vj]}),Ij=c.create,W1=c.getDefault,Y1=c.resetRegistry;var w={baseValues:{lesson_complete:10,quiz_pass:20,quiz_perfect:50,flashcard_review:1,course_complete:200,module_complete:50,streak_bonus:5,achievement_unlock:0,daily_goal_complete:15,first_lesson:25,onboarding_step:5,onboarding_complete:50},scoreThresholds:[{min:90,multiplier:1.5},{min:80,multiplier:1.25},{min:70,multiplier:1},{min:60,multiplier:0.75},{min:0,multiplier:0.5}],streakTiers:[{days:365,bonus:50},{days:180,bonus:30},{days:90,bonus:20},{days:30,bonus:15},{days:14,bonus:10},{days:7,bonus:5},{days:3,bonus:2},{days:1,bonus:0}],perfectScoreMultiplier:1.5,firstAttemptBonus:10,retryPenalty:0.5,speedBonusMultiplier:1.2,speedBonusThreshold:0.8};class Kj{config;constructor(j={}){this.config={...w,...j,baseValues:{...w.baseValues,...j.baseValues},scoreThresholds:j.scoreThresholds||w.scoreThresholds,streakTiers:j.streakTiers||w.streakTiers}}calculate(j){let q=[],W=j.baseXp??this.config.baseValues[j.activity],I=W;if(q.push({source:"base",amount:W}),j.score!==void 0){let Q=this.getScoreMultiplier(j.score);if(Q!==1){let Y=Math.round(W*(Q-1));I+=Y,q.push({source:"score_bonus",amount:Y,multiplier:Q})}if(j.score===100){let Y=Math.round(W*(this.config.perfectScoreMultiplier-1));I+=Y,q.push({source:"perfect_score",amount:Y,multiplier:this.config.perfectScoreMultiplier})}}if(j.attemptNumber===1&&!j.isRetry)I+=this.config.firstAttemptBonus,q.push({source:"first_attempt",amount:this.config.firstAttemptBonus});if(j.isRetry){let Q=Math.round(I*(1-this.config.retryPenalty));I-=Q,q.push({source:"retry_penalty",amount:-Q,multiplier:this.config.retryPenalty})}if(j.currentStreak&&j.currentStreak>0){let Q=this.getStreakBonus(j.currentStreak);if(Q>0)I+=Q,q.push({source:"streak_bonus",amount:Q})}if(W>0)I=Math.max(1,I);return{totalXp:Math.round(I),baseXp:W,breakdown:q}}calculateStreakBonus(j){let q=this.getStreakBonus(j);return{totalXp:q,baseXp:q,breakdown:[{source:"streak_bonus",amount:q}]}}getXpForLevel(j){if(j<=1)return 0;return Math.round(100*Math.pow(j-1,1.5))}getLevelFromXp(j){let q=1,W=this.getXpForLevel(q+1);while(j>=W&&q<1000)q++,W=this.getXpForLevel(q+1);let I=this.getXpForLevel(q),Q=this.getXpForLevel(q+1);return{level:q,xpInLevel:j-I,xpForNextLevel:Q-I}}getScoreMultiplier(j){for(let q of this.config.scoreThresholds)if(j>=q.min)return q.multiplier;return 1}getStreakBonus(j){for(let q of this.config.streakTiers)if(j>=q.days)return q.bonus;return 0}}var IZ={base:"xp.source.base",score_bonus:"xp.source.scoreBonus",perfect_score:"xp.source.perfectScore",first_attempt:"xp.source.firstAttempt",retry_penalty:"xp.source.retryPenalty",streak_bonus:"xp.source.streakBonus"};function X1(j,q){let W=Ij(q),I=IZ[j];return I?W.t(I):j}var k1=new Kj;import{defineEntity as p,defineEntityEnum as Oj,field as Z,index as A}from"@contractspec/lib.schema";var qj=Oj({name:"LearningStyle",values:["VISUAL","AUDITORY","READING","KINESTHETIC","MIXED"],schema:"lssm_learning",description:"Preferred learning style."}),Bj=Oj({name:"RecommendationType",values:["COURSE","LESSON","REVIEW","PRACTICE","ASSESSMENT","DECK"],schema:"lssm_learning",description:"Type of learning recommendation."}),KZ=p({name:"LearnerProfile",description:"AI personalization profile for a learner.",schema:"lssm_learning",map:"learner_profile",fields:{id:Z.id({description:"Unique profile identifier"}),learnerId:Z.foreignKey({description:"Learner"}),learningStyle:Z.enum("LearningStyle",{default:"MIXED",description:"Preferred learning style"}),preferredDifficulty:Z.string({default:'"adaptive"',description:"Difficulty preference"}),preferredSessionLength:Z.int({default:30,description:"Preferred session length in minutes"}),interests:Z.json({isOptional:!0,description:"Topic interests"}),goals:Z.json({isOptional:!0,description:"Learning goals"}),pacePreference:Z.string({default:'"normal"',description:"Learning pace: slow, normal, fast"}),bestTimeOfDay:Z.string({isOptional:!0,description:"Best time for learning"}),averageSessionLength:Z.int({isOptional:!0,description:"Average session length"}),daysActivePerWeek:Z.int({isOptional:!0,description:"Days active per week"}),avgQuizScore:Z.int({isOptional:!0,description:"Average quiz score"}),avgLessonCompletionTime:Z.int({isOptional:!0,description:"Avg lesson completion time"}),strengths:Z.json({isOptional:!0,description:"Identified strengths"}),weaknesses:Z.json({isOptional:!0,description:"Areas for improvement"}),lastAnalyzedAt:Z.dateTime({isOptional:!0,description:"Last AI analysis"}),metadata:Z.json({isOptional:!0,description:"Additional metadata"}),createdAt:Z.createdAt(),updatedAt:Z.updatedAt(),learner:Z.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[A.unique(["learnerId"],{name:"learner_profile_unique"}),A.on(["learningStyle"])],enums:[qj]}),OZ=p({name:"SkillMap",description:"Maps learner proficiency across skills.",schema:"lssm_learning",map:"skill_map",fields:{id:Z.id({description:"Unique skill map identifier"}),learnerId:Z.foreignKey({description:"Learner"}),skillId:Z.string({description:"Skill identifier"}),skillName:Z.string({description:"Skill name"}),skillCategory:Z.string({isOptional:!0,description:"Skill category"}),level:Z.int({default:0,description:"Proficiency level (0-100)"}),confidence:Z.decimal({default:0.5,description:"Confidence in assessment"}),lessonsCompleted:Z.int({default:0,description:"Related lessons completed"}),quizzesCompleted:Z.int({default:0,description:"Related quizzes completed"}),practiceTime:Z.int({default:0,description:"Practice time in minutes"}),lastPracticedAt:Z.dateTime({isOptional:!0,description:"Last practice time"}),learningVelocity:Z.decimal({isOptional:!0,description:"Learning speed for this skill"}),predictedTimeToMastery:Z.int({isOptional:!0,description:"Predicted time to mastery (minutes)"}),createdAt:Z.createdAt(),updatedAt:Z.updatedAt(),learner:Z.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[A.unique(["learnerId","skillId"],{name:"skill_map_unique"}),A.on(["skillId","level"]),A.on(["learnerId","level"])]}),qZ=p({name:"LearningPath",description:"AI-generated personalized learning path.",schema:"lssm_learning",map:"learning_path",fields:{id:Z.id({description:"Unique path identifier"}),learnerId:Z.foreignKey({description:"Learner"}),name:Z.string({description:"Path name"}),description:Z.string({isOptional:!0,description:"Path description"}),goal:Z.string({isOptional:!0,description:"Path goal"}),steps:Z.json({description:"Ordered list of learning steps"}),currentStepIndex:Z.int({default:0,description:"Current step index"}),progress:Z.int({default:0,description:"Completion percentage"}),completedSteps:Z.int({default:0,description:"Steps completed"}),totalSteps:Z.int({default:0,description:"Total steps"}),generatedAt:Z.dateTime({description:"When path was generated"}),adaptedFrom:Z.string({isOptional:!0,description:"Original path ID if adapted"}),generationParams:Z.json({isOptional:!0,description:"AI generation parameters"}),adaptationHistory:Z.json({isOptional:!0,description:"Path adaptation history"}),isActive:Z.boolean({default:!0,description:"Whether path is active"}),isCompleted:Z.boolean({default:!1,description:"Whether path is completed"}),startedAt:Z.dateTime({isOptional:!0,description:"When started"}),completedAt:Z.dateTime({isOptional:!0,description:"When completed"}),estimatedCompletionDate:Z.dateTime({isOptional:!0,description:"Estimated completion"}),createdAt:Z.createdAt(),updatedAt:Z.updatedAt(),learner:Z.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[A.on(["learnerId","isActive"]),A.on(["generatedAt"])]}),BZ=p({name:"Recommendation",description:"AI-powered learning recommendation.",schema:"lssm_learning",map:"recommendation",fields:{id:Z.id({description:"Unique recommendation identifier"}),learnerId:Z.foreignKey({description:"Learner"}),type:Z.enum("RecommendationType",{description:"Recommendation type"}),itemId:Z.string({description:"Recommended item ID"}),itemType:Z.string({description:"Item type (course, lesson, deck, etc.)"}),score:Z.decimal({description:"Recommendation score (0-1)"}),confidence:Z.decimal({description:"Confidence in recommendation"}),reason:Z.string({description:"Human-readable reason"}),factors:Z.json({isOptional:!0,description:"Factors that contributed to recommendation"}),status:Z.string({default:'"pending"',description:"Status: pending, viewed, accepted, dismissed"}),viewedAt:Z.dateTime({isOptional:!0,description:"When viewed"}),acceptedAt:Z.dateTime({isOptional:!0,description:"When accepted"}),dismissedAt:Z.dateTime({isOptional:!0,description:"When dismissed"}),feedback:Z.string({isOptional:!0,description:"User feedback"}),feedbackRating:Z.int({isOptional:!0,description:"Feedback rating (1-5)"}),expiresAt:Z.dateTime({isOptional:!0,description:"When recommendation expires"}),createdAt:Z.createdAt(),updatedAt:Z.updatedAt(),learner:Z.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[A.on(["learnerId","status","score"]),A.on(["type","status"]),A.on(["expiresAt"])],enums:[Bj]}),zZ=p({name:"LearningGap",description:"Identified learning gap.",schema:"lssm_learning",map:"learning_gap",fields:{id:Z.id({description:"Unique gap identifier"}),learnerId:Z.foreignKey({description:"Learner"}),skillId:Z.string({description:"Skill with gap"}),skillName:Z.string({description:"Skill name"}),severity:Z.string({default:'"moderate"',description:"Gap severity: minor, moderate, major"}),confidence:Z.decimal({description:"Confidence in gap detection"}),evidence:Z.json({isOptional:!0,description:"Evidence for gap"}),relatedQuestions:Z.json({isOptional:!0,description:"Questions that revealed gap"}),suggestedRemediation:Z.json({isOptional:!0,description:"Suggested remediation"}),remediationProgress:Z.int({default:0,description:"Remediation progress"}),status:Z.string({default:'"open"',description:"Status: open, in_progress, resolved"}),resolvedAt:Z.dateTime({isOptional:!0,description:"When resolved"}),detectedAt:Z.dateTime({description:"When gap was detected"}),createdAt:Z.createdAt(),updatedAt:Z.updatedAt(),learner:Z.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[A.on(["learnerId","status"]),A.on(["skillId","status"]),A.on(["severity","status"])]}),zj=[KZ,OZ,qZ,BZ,zZ],Dj=[qj,Bj];import{defineEntity as s,defineEntityEnum as T,field as B,index as b}from"@contractspec/lib.schema";var Qj=T({name:"CourseDifficulty",values:["BEGINNER","INTERMEDIATE","ADVANCED","EXPERT"],schema:"lssm_learning",description:"Difficulty level of a course."}),Wj=T({name:"CourseStatus",values:["DRAFT","PUBLISHED","ARCHIVED"],schema:"lssm_learning",description:"Publication status of a course."}),Yj=T({name:"LessonType",values:["CONTENT","VIDEO","INTERACTIVE","QUIZ","PRACTICE","PROJECT"],schema:"lssm_learning",description:"Type of lesson content."}),Jj=T({name:"ContentType",values:["MARKDOWN","VIDEO","AUDIO","EMBED","SCORM","CUSTOM"],schema:"lssm_learning",description:"Type of lesson content format."}),DZ=s({name:"Course",description:"A structured learning course.",schema:"lssm_learning",map:"course",fields:{id:B.id({description:"Unique course identifier"}),title:B.string({description:"Course title"}),slug:B.string({isUnique:!0,description:"URL-friendly slug"}),description:B.string({isOptional:!0,description:"Course description"}),summary:B.string({isOptional:!0,description:"Short summary"}),difficulty:B.enum("CourseDifficulty",{default:"BEGINNER",description:"Difficulty level"}),category:B.string({isOptional:!0,description:"Course category"}),tags:B.json({isOptional:!0,description:"Tags for discovery"}),prerequisites:B.json({isOptional:!0,description:"Required course IDs"}),requiredSkills:B.json({isOptional:!0,description:"Required skill levels"}),estimatedDuration:B.int({isOptional:!0,description:"Estimated duration in minutes"}),thumbnailUrl:B.string({isOptional:!0,description:"Thumbnail image URL"}),coverImageUrl:B.string({isOptional:!0,description:"Cover image URL"}),promoVideoUrl:B.string({isOptional:!0,description:"Promo video URL"}),status:B.enum("CourseStatus",{default:"DRAFT",description:"Publication status"}),publishedAt:B.dateTime({isOptional:!0,description:"When published"}),authorId:B.string({description:"Author user ID"}),orgId:B.string({isOptional:!0,description:"Organization scope"}),isPublic:B.boolean({default:!1,description:"Whether course is publicly accessible"}),isFeatured:B.boolean({default:!1,description:"Whether course is featured"}),certificateEnabled:B.boolean({default:!1,description:"Award certificate on completion"}),metadata:B.json({isOptional:!0,description:"Additional metadata"}),createdAt:B.createdAt(),updatedAt:B.updatedAt(),modules:B.hasMany("CourseModule"),enrollments:B.hasMany("Enrollment")},indexes:[b.on(["orgId","status"]),b.on(["category"]),b.on(["difficulty"]),b.on(["authorId"])],enums:[Qj,Wj]}),QZ=s({name:"CourseModule",description:"A module (section) within a course.",schema:"lssm_learning",map:"course_module",fields:{id:B.id({description:"Unique module identifier"}),courseId:B.foreignKey({description:"Parent course"}),title:B.string({description:"Module title"}),description:B.string({isOptional:!0,description:"Module description"}),order:B.int({default:0,description:"Display order"}),unlockCondition:B.json({isOptional:!0,description:"Conditions to unlock module"}),prerequisiteModuleIds:B.json({isOptional:!0,description:"Required modules to complete first"}),estimatedDuration:B.int({isOptional:!0,description:"Estimated duration in minutes"}),createdAt:B.createdAt(),updatedAt:B.updatedAt(),course:B.belongsTo("Course",["courseId"],["id"],{onDelete:"Cascade"}),lessons:B.hasMany("Lesson"),completions:B.hasMany("ModuleCompletion")},indexes:[b.on(["courseId","order"])]}),WZ=s({name:"Lesson",description:"An individual lesson within a module.",schema:"lssm_learning",map:"lesson",fields:{id:B.id({description:"Unique lesson identifier"}),moduleId:B.foreignKey({description:"Parent module"}),title:B.string({description:"Lesson title"}),description:B.string({isOptional:!0,description:"Lesson description"}),type:B.enum("LessonType",{default:"CONTENT",description:"Lesson type"}),order:B.int({default:0,description:"Display order"}),estimatedDuration:B.int({isOptional:!0,description:"Estimated duration in minutes"}),xpReward:B.int({default:10,description:"XP awarded on completion"}),isFree:B.boolean({default:!1,description:"Whether lesson is free preview"}),isRequired:B.boolean({default:!0,description:"Whether lesson is required for completion"}),metadata:B.json({isOptional:!0,description:"Additional metadata"}),createdAt:B.createdAt(),updatedAt:B.updatedAt(),module:B.belongsTo("CourseModule",["moduleId"],["id"],{onDelete:"Cascade"}),contents:B.hasMany("LessonContent"),progress:B.hasMany("LessonProgress"),quizzes:B.hasMany("Quiz")},indexes:[b.on(["moduleId","order"]),b.on(["type"])],enums:[Yj]}),YZ=s({name:"LessonContent",description:"Content block within a lesson.",schema:"lssm_learning",map:"lesson_content",fields:{id:B.id({description:"Unique content identifier"}),lessonId:B.foreignKey({description:"Parent lesson"}),contentType:B.enum("ContentType",{description:"Content format"}),content:B.string({isOptional:!0,description:"Text content (markdown, etc.)"}),mediaUrl:B.string({isOptional:!0,description:"Media URL for video/audio"}),embedData:B.json({isOptional:!0,description:"Embed data for external content"}),order:B.int({default:0,description:"Display order"}),duration:B.int({isOptional:!0,description:"Content duration in seconds"}),metadata:B.json({isOptional:!0,description:"Additional metadata"}),createdAt:B.createdAt(),updatedAt:B.updatedAt(),lesson:B.belongsTo("Lesson",["lessonId"],["id"],{onDelete:"Cascade"})},indexes:[b.on(["lessonId","order"])],enums:[Jj]}),Fj=[DZ,QZ,WZ,YZ],Xj=[Qj,Wj,Yj,Jj];import{defineEntity as o,defineEntityEnum as JZ,field as z,index as L}from"@contractspec/lib.schema";var kj=JZ({name:"CardRating",values:["AGAIN","HARD","GOOD","EASY"],schema:"lssm_learning",description:"Rating for a flashcard review."}),FZ=o({name:"Deck",description:"A collection of flashcards.",schema:"lssm_learning",map:"deck",fields:{id:z.id({description:"Unique deck identifier"}),ownerId:z.foreignKey({description:"Deck owner (learner)"}),title:z.string({description:"Deck title"}),description:z.string({isOptional:!0,description:"Deck description"}),category:z.string({isOptional:!0,description:"Deck category"}),tags:z.json({isOptional:!0,description:"Tags for discovery"}),isPublic:z.boolean({default:!1,description:"Whether deck is publicly visible"}),cardCount:z.int({default:0,description:"Number of cards"}),coverImageUrl:z.string({isOptional:!0,description:"Cover image URL"}),orgId:z.string({isOptional:!0,description:"Organization scope"}),newCardsPerDay:z.int({default:20,description:"New cards to introduce per day"}),reviewsPerDay:z.int({default:100,description:"Maximum reviews per day"}),metadata:z.json({isOptional:!0,description:"Additional metadata"}),createdAt:z.createdAt(),updatedAt:z.updatedAt(),owner:z.belongsTo("Learner",["ownerId"],["id"],{onDelete:"Cascade"}),cards:z.hasMany("Card")},indexes:[L.on(["ownerId"]),L.on(["isPublic","category"]),L.on(["orgId"])]}),XZ=o({name:"Card",description:"An individual flashcard.",schema:"lssm_learning",map:"card",fields:{id:z.id({description:"Unique card identifier"}),deckId:z.foreignKey({description:"Parent deck"}),front:z.string({description:"Front of card (question)"}),back:z.string({description:"Back of card (answer)"}),hints:z.json({isOptional:!0,description:"Hints for the card"}),explanation:z.string({isOptional:!0,description:"Detailed explanation"}),frontMediaUrl:z.string({isOptional:!0,description:"Media for front"}),backMediaUrl:z.string({isOptional:!0,description:"Media for back"}),audioUrl:z.string({isOptional:!0,description:"Audio pronunciation"}),tags:z.json({isOptional:!0,description:"Card tags"}),difficulty:z.int({default:0,description:"Card difficulty (0-5)"}),order:z.int({default:0,description:"Card order in deck"}),isSuspended:z.boolean({default:!1,description:"Whether card is suspended"}),metadata:z.json({isOptional:!0,description:"Additional metadata"}),createdAt:z.createdAt(),updatedAt:z.updatedAt(),deck:z.belongsTo("Deck",["deckId"],["id"],{onDelete:"Cascade"}),reviews:z.hasMany("CardReview"),schedules:z.hasMany("CardSchedule")},indexes:[L.on(["deckId","order"]),L.on(["isSuspended"])]}),kZ=o({name:"CardReview",description:"A single review of a flashcard.",schema:"lssm_learning",map:"card_review",fields:{id:z.id({description:"Unique review identifier"}),learnerId:z.foreignKey({description:"Reviewer"}),cardId:z.foreignKey({description:"Reviewed card"}),rating:z.enum("CardRating",{description:"Review rating"}),responseTimeMs:z.int({isOptional:!0,description:"Time to respond in ms"}),intervalBefore:z.int({description:"Interval before review (days)"}),easeFactorBefore:z.decimal({description:"Ease factor before review"}),intervalAfter:z.int({description:"Interval after review (days)"}),easeFactorAfter:z.decimal({description:"Ease factor after review"}),reviewType:z.string({default:'"review"',description:"Type: new, learning, review, relearning"}),reviewedAt:z.dateTime({description:"When reviewed"}),createdAt:z.createdAt(),learner:z.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"}),card:z.belongsTo("Card",["cardId"],["id"],{onDelete:"Cascade"})},indexes:[L.on(["learnerId","reviewedAt"]),L.on(["cardId","reviewedAt"]),L.on(["rating"])],enums:[kj]}),UZ=o({name:"CardSchedule",description:"SRS schedule for a learner/card pair.",schema:"lssm_learning",map:"card_schedule",fields:{id:z.id({description:"Unique schedule identifier"}),learnerId:z.foreignKey({description:"Learner"}),cardId:z.foreignKey({description:"Card"}),interval:z.int({default:0,description:"Current interval in days"}),easeFactor:z.decimal({default:2.5,description:"Ease factor (SM-2)"}),repetitions:z.int({default:0,description:"Number of successful repetitions"}),nextReviewAt:z.dateTime({description:"When next review is due"}),lastReviewAt:z.dateTime({isOptional:!0,description:"When last reviewed"}),learningStep:z.int({default:0,description:"Current learning step"}),isGraduated:z.boolean({default:!1,description:"Whether card has graduated"}),isRelearning:z.boolean({default:!1,description:"Whether card is being relearned"}),lapses:z.int({default:0,description:"Number of times card was forgotten"}),reviewCount:z.int({default:0,description:"Total review count"}),createdAt:z.createdAt(),updatedAt:z.updatedAt(),learner:z.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"}),card:z.belongsTo("Card",["cardId"],["id"],{onDelete:"Cascade"})},indexes:[L.unique(["learnerId","cardId"],{name:"card_schedule_unique"}),L.on(["learnerId","nextReviewAt"]),L.on(["nextReviewAt"])]}),Uj=[FZ,XZ,kZ,UZ],Gj=[kj];import{defineEntity as x,defineEntityEnum as Mj,field as $,index as X}from"@contractspec/lib.schema";var Pj=Mj({name:"AchievementType",values:["MILESTONE","STREAK","SKILL","SOCIAL","SPECIAL","SEASONAL"],schema:"lssm_learning",description:"Type of achievement."}),Aj=Mj({name:"LeaderboardPeriod",values:["DAILY","WEEKLY","MONTHLY","ALL_TIME"],schema:"lssm_learning",description:"Leaderboard time period."}),GZ=x({name:"Achievement",description:"An achievement that can be unlocked.",schema:"lssm_learning",map:"achievement",fields:{id:$.id({description:"Unique achievement identifier"}),key:$.string({isUnique:!0,description:"Achievement key"}),name:$.string({description:"Achievement name"}),description:$.string({description:"Achievement description"}),icon:$.string({isOptional:!0,description:"Icon name or URL"}),color:$.string({isOptional:!0,description:"Display color"}),badgeUrl:$.string({isOptional:!0,description:"Badge image URL"}),type:$.enum("AchievementType",{default:"MILESTONE",description:"Achievement type"}),category:$.string({isOptional:!0,description:"Achievement category"}),rarity:$.string({default:'"common"',description:"Rarity: common, rare, epic, legendary"}),xpReward:$.int({default:50,description:"XP awarded"}),condition:$.json({description:"Unlock condition"}),order:$.int({default:0,description:"Display order"}),isHidden:$.boolean({default:!1,description:"Hide until unlocked"}),isActive:$.boolean({default:!0,description:"Whether achievement is active"}),orgId:$.string({isOptional:!0,description:"Organization scope"}),metadata:$.json({isOptional:!0,description:"Additional metadata"}),createdAt:$.createdAt(),updatedAt:$.updatedAt(),learnerAchievements:$.hasMany("LearnerAchievement")},indexes:[X.on(["type"]),X.on(["category"]),X.on(["isActive"]),X.on(["orgId"])],enums:[Pj]}),MZ=x({name:"LearnerAchievement",description:"An achievement unlocked by a learner.",schema:"lssm_learning",map:"learner_achievement",fields:{id:$.id({description:"Unique record identifier"}),learnerId:$.foreignKey({description:"Learner"}),achievementId:$.foreignKey({description:"Achievement"}),xpEarned:$.int({default:0,description:"XP earned"}),progress:$.int({default:100,description:"Progress percentage"}),currentValue:$.int({isOptional:!0,description:"Current value towards goal"}),targetValue:$.int({isOptional:!0,description:"Target value for completion"}),unlockedAt:$.dateTime({description:"When unlocked"}),createdAt:$.createdAt(),learner:$.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"}),achievement:$.belongsTo("Achievement",["achievementId"],["id"],{onDelete:"Cascade"})},indexes:[X.unique(["learnerId","achievementId"],{name:"learner_achievement_unique"}),X.on(["learnerId","unlockedAt"]),X.on(["achievementId"])]}),PZ=x({name:"Streak",description:"Tracks daily learning streaks.",schema:"lssm_learning",map:"streak",fields:{id:$.id({description:"Unique streak identifier"}),learnerId:$.foreignKey({description:"Learner"}),currentStreak:$.int({default:0,description:"Current streak days"}),longestStreak:$.int({default:0,description:"Longest streak ever"}),lastActivityAt:$.dateTime({isOptional:!0,description:"Last learning activity"}),lastActivityDate:$.string({isOptional:!0,description:"Last activity date (YYYY-MM-DD)"}),freezesRemaining:$.int({default:0,description:"Streak freezes available"}),freezeUsedAt:$.dateTime({isOptional:!0,description:"When last freeze was used"}),streakHistory:$.json({isOptional:!0,description:"Historical streak data"}),createdAt:$.createdAt(),updatedAt:$.updatedAt(),learner:$.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[X.unique(["learnerId"],{name:"streak_learner_unique"}),X.on(["currentStreak"]),X.on(["longestStreak"])]}),AZ=x({name:"DailyGoal",description:"Daily XP goal tracking.",schema:"lssm_learning",map:"daily_goal",fields:{id:$.id({description:"Unique goal identifier"}),learnerId:$.foreignKey({description:"Learner"}),date:$.string({description:"Date (YYYY-MM-DD)"}),targetXp:$.int({description:"Target XP for the day"}),currentXp:$.int({default:0,description:"XP earned today"}),isCompleted:$.boolean({default:!1,description:"Whether goal was met"}),completedAt:$.dateTime({isOptional:!0,description:"When goal was completed"}),xpBreakdown:$.json({isOptional:!0,description:"XP sources breakdown"}),createdAt:$.createdAt(),updatedAt:$.updatedAt(),learner:$.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[X.unique(["learnerId","date"],{name:"daily_goal_unique"}),X.on(["date","isCompleted"])]}),CZ=x({name:"LeaderboardEntry",description:"Leaderboard entry for a learner.",schema:"lssm_learning",map:"leaderboard_entry",fields:{id:$.id({description:"Unique entry identifier"}),learnerId:$.foreignKey({description:"Learner"}),periodType:$.enum("LeaderboardPeriod",{description:"Period type"}),periodKey:$.string({description:"Period key (e.g., 2024-W01)"}),xp:$.int({default:0,description:"XP earned in period"}),rank:$.int({isOptional:!0,description:"Rank in leaderboard"}),lessonsCompleted:$.int({default:0,description:"Lessons completed"}),quizzesPassed:$.int({default:0,description:"Quizzes passed"}),cardsReviewed:$.int({default:0,description:"Cards reviewed"}),streakDays:$.int({default:0,description:"Streak days in period"}),league:$.string({isOptional:!0,description:"League tier"}),orgId:$.string({isOptional:!0,description:"Organization scope"}),createdAt:$.createdAt(),updatedAt:$.updatedAt(),learner:$.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[X.unique(["learnerId","periodType","periodKey"],{name:"leaderboard_entry_unique"}),X.on(["periodType","periodKey","xp"]),X.on(["orgId","periodType","periodKey","xp"])],enums:[Aj]}),LZ=x({name:"Heart",description:"Lives/hearts system for quiz attempts.",schema:"lssm_learning",map:"heart",fields:{id:$.id({description:"Unique heart record identifier"}),learnerId:$.foreignKey({description:"Learner"}),current:$.int({default:5,description:"Current hearts"}),max:$.int({default:5,description:"Maximum hearts"}),lastRefillAt:$.dateTime({isOptional:!0,description:"Last refill time"}),nextRefillAt:$.dateTime({isOptional:!0,description:"Next refill time"}),refillIntervalMinutes:$.int({default:240,description:"Minutes between refills"}),infiniteUntil:$.dateTime({isOptional:!0,description:"Infinite hearts until"}),createdAt:$.createdAt(),updatedAt:$.updatedAt(),learner:$.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[X.unique(["learnerId"],{name:"heart_learner_unique"}),X.on(["nextRefillAt"])]}),hZ=x({name:"XPTransaction",description:"Record of XP earned or spent.",schema:"lssm_learning",map:"xp_transaction",fields:{id:$.id({description:"Unique transaction identifier"}),learnerId:$.foreignKey({description:"Learner"}),amount:$.int({description:"XP amount (positive = earned, negative = spent)"}),type:$.string({description:"Transaction type (lesson, quiz, streak, achievement, etc.)"}),sourceType:$.string({isOptional:!0,description:"Source entity type"}),sourceId:$.string({isOptional:!0,description:"Source entity ID"}),description:$.string({isOptional:!0,description:"Human-readable description"}),balanceAfter:$.int({description:"Total XP after transaction"}),createdAt:$.createdAt(),learner:$.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[X.on(["learnerId","createdAt"]),X.on(["type"]),X.on(["sourceType","sourceId"])]}),Cj=[GZ,MZ,PZ,AZ,CZ,LZ,hZ],Lj=[Pj,Aj];import{defineEntity as v,defineEntityEnum as hj,field as H,index as P}from"@contractspec/lib.schema";var gj=hj({name:"EnrollmentStatus",values:["ENROLLED","IN_PROGRESS","COMPLETED","DROPPED","EXPIRED"],schema:"lssm_learning",description:"Status of a course enrollment."}),bj=hj({name:"ProgressStatus",values:["NOT_STARTED","IN_PROGRESS","COMPLETED","SKIPPED"],schema:"lssm_learning",description:"Status of lesson progress."}),gZ=v({name:"Learner",description:"A learner profile.",schema:"lssm_learning",map:"learner",fields:{id:H.id({description:"Unique learner identifier"}),userId:H.string({isUnique:!0,description:"Associated user ID"}),displayName:H.string({isOptional:!0,description:"Display name"}),avatarUrl:H.string({isOptional:!0,description:"Avatar URL"}),bio:H.string({isOptional:!0,description:"Short bio"}),level:H.int({default:1,description:"Current level"}),totalXp:H.int({default:0,description:"Total XP earned"}),currentStreak:H.int({default:0,description:"Current streak days"}),longestStreak:H.int({default:0,description:"Longest streak ever"}),lastActivityAt:H.dateTime({isOptional:!0,description:"Last learning activity"}),locale:H.string({isOptional:!0,description:'Preferred locale for learning content (e.g. "en", "fr", "es")'}),timezone:H.string({default:'"UTC"',description:"Learner timezone"}),dailyGoalXp:H.int({default:50,description:"Daily XP goal"}),reminderEnabled:H.boolean({default:!0,description:"Enable reminders"}),reminderTime:H.string({isOptional:!0,description:"Preferred reminder time"}),orgId:H.string({isOptional:!0,description:"Organization scope"}),metadata:H.json({isOptional:!0,description:"Additional metadata"}),createdAt:H.createdAt(),updatedAt:H.updatedAt(),enrollments:H.hasMany("Enrollment"),lessonProgress:H.hasMany("LessonProgress"),achievements:H.hasMany("LearnerAchievement"),decks:H.hasMany("Deck"),profile:H.hasOne("LearnerProfile")},indexes:[P.on(["orgId"]),P.on(["totalXp"]),P.on(["level"]),P.on(["currentStreak"])]}),bZ=v({name:"Enrollment",description:"A learner enrollment in a course.",schema:"lssm_learning",map:"enrollment",fields:{id:H.id({description:"Unique enrollment identifier"}),learnerId:H.foreignKey({description:"Enrolled learner"}),courseId:H.foreignKey({description:"Enrolled course"}),status:H.enum("EnrollmentStatus",{default:"ENROLLED",description:"Enrollment status"}),progress:H.int({default:0,description:"Completion percentage (0-100)"}),completedLessons:H.int({default:0,description:"Number of completed lessons"}),totalLessons:H.int({default:0,description:"Total lessons in course"}),xpEarned:H.int({default:0,description:"XP earned in this course"}),startedAt:H.dateTime({isOptional:!0,description:"When learner started"}),completedAt:H.dateTime({isOptional:!0,description:"When learner completed"}),lastAccessedAt:H.dateTime({isOptional:!0,description:"Last access time"}),certificateId:H.string({isOptional:!0,description:"Issued certificate ID"}),metadata:H.json({isOptional:!0,description:"Additional metadata"}),createdAt:H.createdAt(),updatedAt:H.updatedAt(),learner:H.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"}),course:H.belongsTo("Course",["courseId"],["id"],{onDelete:"Cascade"})},indexes:[P.unique(["learnerId","courseId"],{name:"enrollment_unique"}),P.on(["learnerId","status"]),P.on(["courseId","status"])],enums:[gj]}),xZ=v({name:"LessonProgress",description:"Progress tracking for a lesson.",schema:"lssm_learning",map:"lesson_progress",fields:{id:H.id({description:"Unique progress identifier"}),learnerId:H.foreignKey({description:"Learner"}),lessonId:H.foreignKey({description:"Lesson"}),status:H.enum("ProgressStatus",{default:"NOT_STARTED",description:"Progress status"}),progress:H.int({default:0,description:"Completion percentage (0-100)"}),score:H.int({isOptional:!0,description:"Score achieved (for quizzes)"}),attempts:H.int({default:0,description:"Number of attempts"}),bestScore:H.int({isOptional:!0,description:"Best score across attempts"}),timeSpent:H.int({default:0,description:"Time spent in seconds"}),xpEarned:H.int({default:0,description:"XP earned from this lesson"}),startedAt:H.dateTime({isOptional:!0,description:"When started"}),completedAt:H.dateTime({isOptional:!0,description:"When completed"}),lastAccessedAt:H.dateTime({isOptional:!0,description:"Last access time"}),bookmarks:H.json({isOptional:!0,description:"Content bookmarks"}),notes:H.string({isOptional:!0,description:"Learner notes"}),createdAt:H.createdAt(),updatedAt:H.updatedAt(),learner:H.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"}),lesson:H.belongsTo("Lesson",["lessonId"],["id"],{onDelete:"Cascade"})},indexes:[P.unique(["learnerId","lessonId"],{name:"lesson_progress_unique"}),P.on(["learnerId","status"]),P.on(["lessonId"])],enums:[bj]}),NZ=v({name:"ModuleCompletion",description:"Module completion record.",schema:"lssm_learning",map:"module_completion",fields:{id:H.id({description:"Unique completion identifier"}),learnerId:H.foreignKey({description:"Learner"}),moduleId:H.foreignKey({description:"Module"}),score:H.int({isOptional:!0,description:"Average score"}),xpEarned:H.int({default:0,description:"XP earned"}),timeSpent:H.int({default:0,description:"Time spent in seconds"}),completedAt:H.dateTime({description:"When completed"}),createdAt:H.createdAt(),learner:H.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"}),module:H.belongsTo("CourseModule",["moduleId"],["id"],{onDelete:"Cascade"})},indexes:[P.unique(["learnerId","moduleId"],{name:"module_completion_unique"}),P.on(["learnerId","completedAt"])]}),pZ=v({name:"Certificate",description:"Course completion certificate.",schema:"lssm_learning",map:"certificate",fields:{id:H.id({description:"Unique certificate identifier"}),learnerId:H.foreignKey({description:"Certificate holder"}),courseId:H.foreignKey({description:"Completed course"}),enrollmentId:H.foreignKey({description:"Associated enrollment"}),certificateNumber:H.string({isUnique:!0,description:"Unique certificate number"}),title:H.string({description:"Certificate title"}),description:H.string({isOptional:!0,description:"Certificate description"}),score:H.int({isOptional:!0,description:"Final score"}),grade:H.string({isOptional:!0,description:"Grade awarded"}),issuedAt:H.dateTime({description:"When issued"}),validUntil:H.dateTime({isOptional:!0,description:"Expiration date"}),verificationUrl:H.string({isOptional:!0,description:"Verification URL"}),credentialHash:H.string({isOptional:!0,description:"Credential hash for verification"}),isRevoked:H.boolean({default:!1,description:"Whether certificate is revoked"}),revokedAt:H.dateTime({isOptional:!0,description:"When revoked"}),revokedReason:H.string({isOptional:!0,description:"Revocation reason"}),metadata:H.json({isOptional:!0,description:"Additional metadata"}),createdAt:H.createdAt(),learner:H.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"}),course:H.belongsTo("Course",["courseId"],["id"],{onDelete:"Cascade"})},indexes:[P.on(["learnerId"]),P.on(["courseId"]),P.on(["issuedAt"])]}),xj=[gZ,bZ,xZ,NZ,pZ],Nj=[gj,bj];import{defineEntity as u,defineEntityEnum as vZ,field as K,index as h}from"@contractspec/lib.schema";var pj=vZ({name:"OnboardingStepStatus",values:["PENDING","IN_PROGRESS","COMPLETED","SKIPPED"],schema:"lssm_learning",description:"Status of an onboarding step."}),RZ=u({name:"OnboardingTrack",description:"An onboarding track for a product.",schema:"lssm_learning",map:"onboarding_track",fields:{id:K.id({description:"Unique track identifier"}),productId:K.string({description:"Product this track is for"}),name:K.string({description:"Track name"}),description:K.string({isOptional:!0,description:"Track description"}),targetUserSegment:K.string({isOptional:!0,description:"Target user segment"}),targetRole:K.string({isOptional:!0,description:"Target user role"}),welcomeTitle:K.string({isOptional:!0,description:"Welcome message title"}),welcomeMessage:K.string({isOptional:!0,description:"Welcome message"}),completionTitle:K.string({isOptional:!0,description:"Completion message title"}),completionMessage:K.string({isOptional:!0,description:"Completion message"}),isActive:K.boolean({default:!0,description:"Whether track is active"}),isRequired:K.boolean({default:!1,description:"Whether track is required"}),canSkip:K.boolean({default:!0,description:"Whether steps can be skipped"}),totalXp:K.int({default:100,description:"Total XP for completing track"}),completionXpBonus:K.int({isOptional:!0,description:"Bonus XP for completing track"}),completionBadgeKey:K.string({isOptional:!0,description:"Badge awarded on completion"}),streakHoursWindow:K.int({isOptional:!0,description:"Hours window to finish for streak bonus"}),streakBonusXp:K.int({isOptional:!0,description:"Bonus XP if completed within streak window"}),orgId:K.string({isOptional:!0,description:"Organization scope"}),metadata:K.json({isOptional:!0,description:"Additional metadata"}),createdAt:K.createdAt(),updatedAt:K.updatedAt(),steps:K.hasMany("OnboardingStep"),progress:K.hasMany("OnboardingProgress")},indexes:[h.on(["productId","isActive"]),h.on(["orgId"]),h.unique(["productId","targetUserSegment","targetRole"],{name:"onboarding_track_target"})]}),wZ=u({name:"OnboardingStep",description:"A step in an onboarding track.",schema:"lssm_learning",map:"onboarding_step",fields:{id:K.id({description:"Unique step identifier"}),trackId:K.foreignKey({description:"Parent track"}),title:K.string({description:"Step title"}),description:K.string({isOptional:!0,description:"Step description"}),instructions:K.string({isOptional:!0,description:"How to complete the step"}),helpUrl:K.string({isOptional:!0,description:"Link to help documentation"}),order:K.int({default:0,description:"Display order"}),triggerEvent:K.string({isOptional:!0,description:"Event that triggers step start"}),completionEvent:K.string({description:"Event that completes the step"}),completionEventVersion:K.int({isOptional:!0,description:"Version of the completion event"}),completionSourceModule:K.string({isOptional:!0,description:"Module emitting the completion event"}),completionEventFilter:K.json({isOptional:!0,description:"Filter for completion event"}),actionUrl:K.string({isOptional:!0,description:"URL to navigate to complete"}),actionLabel:K.string({isOptional:!0,description:"Action button label"}),highlightSelector:K.string({isOptional:!0,description:"CSS selector to highlight"}),tooltipPosition:K.string({isOptional:!0,description:"Tooltip position"}),xpReward:K.int({default:10,description:"XP for completing step"}),isRequired:K.boolean({default:!0,description:"Whether step is required"}),canSkip:K.boolean({default:!0,description:"Whether step can be skipped"}),metadata:K.json({isOptional:!0,description:"Additional metadata"}),createdAt:K.createdAt(),updatedAt:K.updatedAt(),track:K.belongsTo("OnboardingTrack",["trackId"],["id"],{onDelete:"Cascade"})},indexes:[h.on(["trackId","order"]),h.on(["completionEvent"])]}),sZ=u({name:"OnboardingProgress",description:"Tracks user progress through an onboarding track.",schema:"lssm_learning",map:"onboarding_progress",fields:{id:K.id({description:"Unique progress identifier"}),learnerId:K.foreignKey({description:"Learner"}),trackId:K.foreignKey({description:"Onboarding track"}),currentStepId:K.string({isOptional:!0,description:"Current step ID"}),completedSteps:K.json({default:"[]",description:"Array of completed step IDs"}),skippedSteps:K.json({default:"[]",description:"Array of skipped step IDs"}),progress:K.int({default:0,description:"Completion percentage (0-100)"}),isCompleted:K.boolean({default:!1,description:"Whether track is completed"}),xpEarned:K.int({default:0,description:"XP earned from track"}),startedAt:K.dateTime({description:"When user started"}),completedAt:K.dateTime({isOptional:!0,description:"When user completed"}),lastActivityAt:K.dateTime({isOptional:!0,description:"Last activity"}),isDismissed:K.boolean({default:!1,description:"Whether user dismissed onboarding"}),dismissedAt:K.dateTime({isOptional:!0,description:"When dismissed"}),metadata:K.json({isOptional:!0,description:"Additional metadata"}),createdAt:K.createdAt(),updatedAt:K.updatedAt(),learner:K.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"}),track:K.belongsTo("OnboardingTrack",["trackId"],["id"],{onDelete:"Cascade"})},indexes:[h.unique(["learnerId","trackId"],{name:"onboarding_progress_unique"}),h.on(["learnerId","isCompleted"]),h.on(["trackId"])],enums:[pj]}),TZ=u({name:"OnboardingStepCompletion",description:"Individual step completion record.",schema:"lssm_learning",map:"onboarding_step_completion",fields:{id:K.id({description:"Unique completion identifier"}),progressId:K.foreignKey({description:"Parent progress record"}),stepId:K.foreignKey({description:"Completed step"}),status:K.enum("OnboardingStepStatus",{description:"Completion status"}),xpEarned:K.int({default:0,description:"XP earned"}),triggeringEvent:K.string({isOptional:!0,description:"Event that triggered completion"}),eventPayload:K.json({isOptional:!0,description:"Event payload"}),completedAt:K.dateTime({description:"When completed"}),createdAt:K.createdAt(),progress:K.belongsTo("OnboardingProgress",["progressId"],["id"],{onDelete:"Cascade"}),step:K.belongsTo("OnboardingStep",["stepId"],["id"],{onDelete:"Cascade"})},indexes:[h.unique(["progressId","stepId"],{name:"step_completion_unique"}),h.on(["completedAt"])]}),vj=[RZ,wZ,sZ,TZ],Rj=[pj];import{defineEntity as R,defineEntityEnum as m,field as _,index as C}from"@contractspec/lib.schema";var wj=m({name:"QuestionType",values:["MULTIPLE_CHOICE","TRUE_FALSE","FILL_BLANK","MATCHING","SHORT_ANSWER","CODE"],schema:"lssm_learning",description:"Type of quiz question."}),sj=m({name:"QuizStatus",values:["DRAFT","PUBLISHED","ARCHIVED"],schema:"lssm_learning",description:"Publication status of a quiz."}),Tj=m({name:"AttemptStatus",values:["IN_PROGRESS","COMPLETED","TIMED_OUT","ABANDONED"],schema:"lssm_learning",description:"Status of a quiz attempt."}),oZ=R({name:"Quiz",description:"A quiz assessment.",schema:"lssm_learning",map:"quiz",fields:{id:_.id({description:"Unique quiz identifier"}),lessonId:_.foreignKey({isOptional:!0,description:"Associated lesson"}),title:_.string({description:"Quiz title"}),description:_.string({isOptional:!0,description:"Quiz description"}),instructions:_.string({isOptional:!0,description:"Quiz instructions"}),passingScore:_.int({default:70,description:"Passing score percentage"}),timeLimit:_.int({isOptional:!0,description:"Time limit in seconds"}),maxAttempts:_.int({isOptional:!0,description:"Maximum attempts allowed"}),shuffleQuestions:_.boolean({default:!1,description:"Shuffle question order"}),shuffleOptions:_.boolean({default:!1,description:"Shuffle answer options"}),showCorrectAnswers:_.boolean({default:!0,description:"Show correct answers after"}),showExplanations:_.boolean({default:!0,description:"Show explanations after"}),status:_.enum("QuizStatus",{default:"DRAFT",description:"Publication status"}),totalPoints:_.int({default:0,description:"Total points available"}),xpReward:_.int({default:20,description:"XP for passing"}),orgId:_.string({isOptional:!0,description:"Organization scope"}),metadata:_.json({isOptional:!0,description:"Additional metadata"}),createdAt:_.createdAt(),updatedAt:_.updatedAt(),lesson:_.belongsTo("Lesson",["lessonId"],["id"],{onDelete:"Cascade"}),questions:_.hasMany("Question"),attempts:_.hasMany("QuizAttempt")},indexes:[C.on(["lessonId"]),C.on(["status"]),C.on(["orgId"])],enums:[sj]}),uZ=R({name:"Question",description:"A quiz question.",schema:"lssm_learning",map:"question",fields:{id:_.id({description:"Unique question identifier"}),quizId:_.foreignKey({description:"Parent quiz"}),type:_.enum("QuestionType",{description:"Question type"}),content:_.string({description:"Question text"}),mediaUrl:_.string({isOptional:!0,description:"Question media"}),points:_.int({default:1,description:"Points for correct answer"}),codeLanguage:_.string({isOptional:!0,description:"Programming language"}),codeTemplate:_.string({isOptional:!0,description:"Starter code template"}),testCases:_.json({isOptional:!0,description:"Test cases for code validation"}),explanation:_.string({isOptional:!0,description:"Explanation of correct answer"}),hint:_.string({isOptional:!0,description:"Hint for the question"}),order:_.int({default:0,description:"Display order"}),skillId:_.string({isOptional:!0,description:"Associated skill"}),metadata:_.json({isOptional:!0,description:"Additional metadata"}),createdAt:_.createdAt(),updatedAt:_.updatedAt(),quiz:_.belongsTo("Quiz",["quizId"],["id"],{onDelete:"Cascade"}),options:_.hasMany("QuestionOption")},indexes:[C.on(["quizId","order"]),C.on(["type"]),C.on(["skillId"])],enums:[wj]}),yZ=R({name:"QuestionOption",description:"An answer option for a question.",schema:"lssm_learning",map:"question_option",fields:{id:_.id({description:"Unique option identifier"}),questionId:_.foreignKey({description:"Parent question"}),content:_.string({description:"Option text"}),matchContent:_.string({isOptional:!0,description:"Match pair content"}),isCorrect:_.boolean({default:!1,description:"Whether option is correct"}),feedback:_.string({isOptional:!0,description:"Feedback when selected"}),order:_.int({default:0,description:"Display order"}),metadata:_.json({isOptional:!0,description:"Additional metadata"}),createdAt:_.createdAt(),updatedAt:_.updatedAt(),question:_.belongsTo("Question",["questionId"],["id"],{onDelete:"Cascade"})},indexes:[C.on(["questionId","order"])]}),SZ=R({name:"QuizAttempt",description:"A learner quiz attempt.",schema:"lssm_learning",map:"quiz_attempt",fields:{id:_.id({description:"Unique attempt identifier"}),learnerId:_.foreignKey({description:"Learner"}),quizId:_.foreignKey({description:"Quiz"}),status:_.enum("AttemptStatus",{default:"IN_PROGRESS",description:"Attempt status"}),score:_.int({isOptional:!0,description:"Score achieved"}),percentageScore:_.int({isOptional:!0,description:"Percentage score"}),passed:_.boolean({isOptional:!0,description:"Whether passed"}),totalQuestions:_.int({default:0,description:"Total questions"}),answeredQuestions:_.int({default:0,description:"Questions answered"}),correctAnswers:_.int({default:0,description:"Correct answers"}),answers:_.json({isOptional:!0,description:"Submitted answers"}),xpEarned:_.int({default:0,description:"XP earned"}),timeSpent:_.int({default:0,description:"Time spent in seconds"}),startedAt:_.dateTime({description:"When started"}),completedAt:_.dateTime({isOptional:!0,description:"When completed"}),attemptNumber:_.int({default:1,description:"Which attempt this is"}),metadata:_.json({isOptional:!0,description:"Additional metadata"}),createdAt:_.createdAt(),updatedAt:_.updatedAt(),learner:_.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"}),quiz:_.belongsTo("Quiz",["quizId"],["id"],{onDelete:"Cascade"})},indexes:[C.on(["learnerId","quizId"]),C.on(["learnerId","status"]),C.on(["quizId","status"])],enums:[Tj]}),rZ=R({name:"SkillAssessment",description:"Assessment of a skill based on quiz performance.",schema:"lssm_learning",map:"skill_assessment",fields:{id:_.id({description:"Unique assessment identifier"}),learnerId:_.foreignKey({description:"Learner"}),skillId:_.string({description:"Skill identifier"}),skillName:_.string({description:"Skill name"}),level:_.int({default:1,description:"Proficiency level (1-5)"}),score:_.int({default:0,description:"Assessment score (0-100)"}),confidence:_.decimal({default:0.5,description:"Confidence in assessment"}),questionsAnswered:_.int({default:0,description:"Total questions answered"}),questionsCorrect:_.int({default:0,description:"Total correct"}),assessedAt:_.dateTime({description:"Last assessment time"}),createdAt:_.createdAt(),updatedAt:_.updatedAt(),learner:_.belongsTo("Learner",["learnerId"],["id"],{onDelete:"Cascade"})},indexes:[C.unique(["learnerId","skillId"],{name:"skill_assessment_unique"}),C.on(["learnerId","level"]),C.on(["skillId"])]}),oj=[oZ,uZ,yZ,SZ,rZ],uj=[wj,sj,Tj];var cZ=[...Fj,...xj,...vj,...Uj,...oj,...Cj,...zj],mZ=[...Xj,...Nj,...Rj,...Gj,...uj,...Lj,...Dj],t1={moduleId:"@contractspec/module.learning-journey",entities:cZ,enums:mZ};import{defineEvent as U}from"@contractspec/lib.contracts-spec";import{defineSchemaModel as G,ScalarTypeEnum as V}from"@contractspec/lib.schema";var EZ=G({name:"CoursePublishedEventPayload",description:"Payload when a course is published",fields:{courseId:{type:V.String_unsecure(),isOptional:!1},title:{type:V.String_unsecure(),isOptional:!1},authorId:{type:V.String_unsecure(),isOptional:!1},publishedAt:{type:V.DateTime(),isOptional:!1}}}),tZ=U({meta:{key:"course.published",version:"1.0.0",description:"A course has been published.",stability:"stable",owners:[...J],tags:["learning","course"]},payload:EZ}),fZ=G({name:"EnrollmentCreatedEventPayload",description:"Payload when a learner enrolls in a course",fields:{enrollmentId:{type:V.String_unsecure(),isOptional:!1},learnerId:{type:V.String_unsecure(),isOptional:!1},courseId:{type:V.String_unsecure(),isOptional:!1},enrolledAt:{type:V.DateTime(),isOptional:!1}}}),aZ=U({meta:{key:"enrollment.created",version:"1.0.0",description:"A learner has enrolled in a course.",stability:"stable",owners:[...J],tags:["learning","enrollment"]},payload:fZ}),nZ=G({name:"LessonCompletedEventPayload",description:"Payload when a lesson is completed",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},lessonId:{type:V.String_unsecure(),isOptional:!1},courseId:{type:V.String_unsecure(),isOptional:!1},score:{type:V.Int_unsecure(),isOptional:!0},xpEarned:{type:V.Int_unsecure(),isOptional:!1},timeSpent:{type:V.Int_unsecure(),isOptional:!1},completedAt:{type:V.DateTime(),isOptional:!1}}}),lZ=U({meta:{key:"lesson.completed",version:"1.0.0",description:"A learner has completed a lesson.",stability:"stable",owners:[...J],tags:["learning","progress","lesson"]},payload:nZ}),dZ=G({name:"CourseCompletedEventPayload",description:"Payload when a course is completed",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},courseId:{type:V.String_unsecure(),isOptional:!1},enrollmentId:{type:V.String_unsecure(),isOptional:!1},score:{type:V.Int_unsecure(),isOptional:!0},xpEarned:{type:V.Int_unsecure(),isOptional:!1},certificateId:{type:V.String_unsecure(),isOptional:!0},completedAt:{type:V.DateTime(),isOptional:!1}}}),iZ=U({meta:{key:"course.completed",version:"1.0.0",description:"A learner has completed a course.",stability:"stable",owners:[...J],tags:["learning","progress","course"]},payload:dZ}),eZ=G({name:"OnboardingStartedEventPayload",description:"Payload when onboarding starts",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},trackId:{type:V.String_unsecure(),isOptional:!1},productId:{type:V.String_unsecure(),isOptional:!1},startedAt:{type:V.DateTime(),isOptional:!1}}}),j0=U({meta:{key:"onboarding.started",version:"1.0.0",description:"A learner has started onboarding.",stability:"stable",owners:[...J],tags:["learning","onboarding"]},payload:eZ}),Z0=G({name:"OnboardingStepCompletedEventPayload",description:"Payload when an onboarding step is completed",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},trackId:{type:V.String_unsecure(),isOptional:!1},stepId:{type:V.String_unsecure(),isOptional:!1},triggeringEvent:{type:V.String_unsecure(),isOptional:!0},xpEarned:{type:V.Int_unsecure(),isOptional:!1},completedAt:{type:V.DateTime(),isOptional:!1}}}),$0=U({meta:{key:"onboarding.step_completed",version:"1.0.0",description:"An onboarding step has been completed.",stability:"stable",owners:[...J],tags:["learning","onboarding"]},payload:Z0}),H0=G({name:"OnboardingCompletedEventPayload",description:"Payload when onboarding is completed",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},trackId:{type:V.String_unsecure(),isOptional:!1},productId:{type:V.String_unsecure(),isOptional:!1},xpEarned:{type:V.Int_unsecure(),isOptional:!1},completedAt:{type:V.DateTime(),isOptional:!1}}}),V0=U({meta:{key:"onboarding.completed",version:"1.0.0",description:"A learner has completed onboarding.",stability:"stable",owners:[...J],tags:["learning","onboarding"]},payload:H0}),_0=G({name:"CardReviewedEventPayload",description:"Payload when a flashcard is reviewed",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},cardId:{type:V.String_unsecure(),isOptional:!1},deckId:{type:V.String_unsecure(),isOptional:!1},rating:{type:V.String_unsecure(),isOptional:!1},responseTimeMs:{type:V.Int_unsecure(),isOptional:!0},reviewedAt:{type:V.DateTime(),isOptional:!1}}}),I0=U({meta:{key:"flashcard.reviewed",version:"1.0.0",description:"A flashcard has been reviewed.",stability:"stable",owners:[...J],tags:["learning","flashcards"]},payload:_0}),K0=G({name:"QuizStartedEventPayload",description:"Payload when a quiz is started",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},quizId:{type:V.String_unsecure(),isOptional:!1},attemptId:{type:V.String_unsecure(),isOptional:!1},attemptNumber:{type:V.Int_unsecure(),isOptional:!1},startedAt:{type:V.DateTime(),isOptional:!1}}}),O0=U({meta:{key:"quiz.started",version:"1.0.0",description:"A quiz attempt has started.",stability:"stable",owners:[...J],tags:["learning","quiz"]},payload:K0}),q0=G({name:"QuizCompletedEventPayload",description:"Payload when a quiz is completed",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},quizId:{type:V.String_unsecure(),isOptional:!1},attemptId:{type:V.String_unsecure(),isOptional:!1},score:{type:V.Int_unsecure(),isOptional:!1},percentageScore:{type:V.Int_unsecure(),isOptional:!1},passed:{type:V.Boolean(),isOptional:!1},xpEarned:{type:V.Int_unsecure(),isOptional:!1},timeSpent:{type:V.Int_unsecure(),isOptional:!1},completedAt:{type:V.DateTime(),isOptional:!1}}}),B0=U({meta:{key:"quiz.completed",version:"1.0.0",description:"A quiz attempt has been completed.",stability:"stable",owners:[...J],tags:["learning","quiz"]},payload:q0}),z0=G({name:"XpEarnedEventPayload",description:"Payload when XP is earned",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},amount:{type:V.Int_unsecure(),isOptional:!1},source:{type:V.String_unsecure(),isOptional:!1},sourceId:{type:V.String_unsecure(),isOptional:!0},totalXp:{type:V.Int_unsecure(),isOptional:!1},earnedAt:{type:V.DateTime(),isOptional:!1}}}),D0=U({meta:{key:"xp.earned",version:"1.0.0",description:"XP has been earned.",stability:"stable",owners:[...J],tags:["learning","gamification","xp"]},payload:z0}),Q0=G({name:"LevelUpEventPayload",description:"Payload when a learner levels up",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},previousLevel:{type:V.Int_unsecure(),isOptional:!1},newLevel:{type:V.Int_unsecure(),isOptional:!1},totalXp:{type:V.Int_unsecure(),isOptional:!1},leveledUpAt:{type:V.DateTime(),isOptional:!1}}}),W0=U({meta:{key:"level.up",version:"1.0.0",description:"A learner has leveled up.",stability:"stable",owners:[...J],tags:["learning","gamification","level"]},payload:Q0}),Y0=G({name:"StreakUpdatedEventPayload",description:"Payload when a streak is updated",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},previousStreak:{type:V.Int_unsecure(),isOptional:!1},currentStreak:{type:V.Int_unsecure(),isOptional:!1},longestStreak:{type:V.Int_unsecure(),isOptional:!1},freezeUsed:{type:V.Boolean(),isOptional:!1},updatedAt:{type:V.DateTime(),isOptional:!1}}}),J0=U({meta:{key:"streak.updated",version:"1.0.0",description:"A streak has been updated.",stability:"stable",owners:[...J],tags:["learning","gamification","streak"]},payload:Y0}),F0=G({name:"AchievementUnlockedEventPayload",description:"Payload when an achievement is unlocked",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},achievementId:{type:V.String_unsecure(),isOptional:!1},achievementKey:{type:V.String_unsecure(),isOptional:!1},achievementName:{type:V.String_unsecure(),isOptional:!1},xpEarned:{type:V.Int_unsecure(),isOptional:!1},unlockedAt:{type:V.DateTime(),isOptional:!1}}}),X0=U({meta:{key:"achievement.unlocked",version:"1.0.0",description:"An achievement has been unlocked.",stability:"stable",owners:[...J],tags:["learning","gamification","achievement"]},payload:F0}),k0=G({name:"DailyGoalCompletedEventPayload",description:"Payload when a daily goal is completed",fields:{learnerId:{type:V.String_unsecure(),isOptional:!1},date:{type:V.String_unsecure(),isOptional:!1},targetXp:{type:V.Int_unsecure(),isOptional:!1},actualXp:{type:V.Int_unsecure(),isOptional:!1},completedAt:{type:V.DateTime(),isOptional:!1}}}),U0=U({meta:{key:"daily_goal.completed",version:"1.0.0",description:"A daily goal has been completed.",stability:"stable",owners:[...J],tags:["learning","gamification","goal"]},payload:k0}),G0=G({name:"CertificateIssuedEventPayload",description:"Payload when a certificate is issued",fields:{certificateId:{type:V.String_unsecure(),isOptional:!1},learnerId:{type:V.String_unsecure(),isOptional:!1},courseId:{type:V.String_unsecure(),isOptional:!1},certificateNumber:{type:V.String_unsecure(),isOptional:!1},issuedAt:{type:V.DateTime(),isOptional:!1}}}),M0=U({meta:{key:"certificate.issued",version:"1.0.0",description:"A certificate has been issued.",stability:"stable",owners:[...J],tags:["learning","certificate"]},payload:G0}),V4={CoursePublishedEvent:tZ,EnrollmentCreatedEvent:aZ,LessonCompletedEvent:lZ,CourseCompletedEvent:iZ,OnboardingStartedEvent:j0,OnboardingStepCompletedEvent:$0,OnboardingCompletedEvent:V0,CardReviewedEvent:I0,QuizStartedEvent:O0,QuizCompletedEvent:B0,XpEarnedEvent:D0,LevelUpEvent:W0,StreakUpdatedEvent:J0,AchievementUnlockedEvent:X0,DailyGoalCompletedEvent:U0,CertificateIssuedEvent:M0};import{defineFeature as P0}from"@contractspec/lib.contracts-spec";var K4=P0({meta:{key:"learning-journey",version:"1.0.0",title:"Learning Journey",description:"Learning platform with courses, onboarding, flashcards, and gamification",domain:"learning",owners:["@platform.learning-journey"],tags:["learning","onboarding","courses","flashcards","gamification"],stability:"stable"},operations:[{key:"learning.onboarding.recordEvent",version:"1.0.0"},{key:"learning.onboarding.listTracks",version:"1.0.0"},{key:"learning.onboarding.getProgress",version:"1.0.0"},{key:"learning.enroll",version:"1.0.0"},{key:"learning.completeLesson",version:"1.0.0"},{key:"learning.submitCardReview",version:"1.0.0"},{key:"learning.getDueCards",version:"1.0.0"},{key:"learning.getDashboard",version:"1.0.0"}],events:[{key:"course.published",version:"1.0.0"},{key:"course.completed",version:"1.0.0"},{key:"enrollment.created",version:"1.0.0"},{key:"lesson.completed",version:"1.0.0"},{key:"onboarding.started",version:"1.0.0"},{key:"onboarding.step_completed",version:"1.0.0"},{key:"onboarding.completed",version:"1.0.0"},{key:"flashcard.reviewed",version:"1.0.0"},{key:"quiz.started",version:"1.0.0"},{key:"quiz.completed",version:"1.0.0"},{key:"xp.earned",version:"1.0.0"},{key:"level.up",version:"1.0.0"},{key:"streak.updated",version:"1.0.0"},{key:"achievement.unlocked",version:"1.0.0"},{key:"daily_goal.completed",version:"1.0.0"},{key:"certificate.issued",version:"1.0.0"}],presentations:[],opToPresentation:[],presentationsTargets:[],capabilities:{provides:[{key:"learning-journey",version:"1.0.0"},{key:"onboarding",version:"1.0.0"},{key:"gamification",version:"1.0.0"}],requires:[{key:"identity",version:"1.0.0"}]}});export{k1 as xpEngine,$1 as streakEngine,j1 as srsEngine,uj as quizEnums,oj as quizEntities,Rj as onboardingEnums,vj as onboardingEntities,t1 as learningJourneySchemaContribution,mZ as learningJourneyEnums,cZ as learningJourneyEntities,Nj as learnerEnums,xj as learnerEntities,X1 as getXpSourceLabel,Lj as gamificationEnums,Cj as gamificationEntities,Gj as flashcardEnums,Uj as flashcardEntities,Xj as courseEnums,Fj as courseEntities,Dj as aiEnums,zj as aiEntities,D0 as XpEarnedEvent,hZ as XPTransactionEntity,Kj as XPEngine,N as SuccessOutput,f as SubmitCardReviewInput,c0 as SubmitCardReviewContract,J0 as StreakUpdatedEvent,PZ as StreakEntity,$j as StreakEngine,OZ as SkillMapEntity,rZ as SkillAssessmentEntity,Zj as SRSEngine,s0 as RecordOnboardingEventContract,Bj as RecommendationTypeEnum,BZ as RecommendationEntity,sj as QuizStatusEnum,O0 as QuizStartedEvent,oZ as QuizEntity,B0 as QuizCompletedEvent,SZ as QuizAttemptEntity,wj as QuestionTypeEnum,yZ as QuestionOptionEntity,uZ as QuestionEntity,bj as ProgressStatusEnum,L0 as ProgressModel,tj as OnboardingTrackModel,RZ as OnboardingTrackEntity,pj as OnboardingStepStatusEnum,fj as OnboardingStepProgressModel,Ej as OnboardingStepModel,wZ as OnboardingStepEntity,TZ as OnboardingStepCompletionEntity,$0 as OnboardingStepCompletedEvent,j0 as OnboardingStartedEvent,e as OnboardingProgressModel,sZ as OnboardingProgressEntity,V0 as OnboardingCompletedEvent,NZ as ModuleCompletionEntity,R0 as ListOnboardingTracksContract,W0 as LevelUpEvent,Yj as LessonTypeEnum,xZ as LessonProgressEntity,WZ as LessonEntity,YZ as LessonContentEntity,lZ as LessonCompletedEvent,qj as LearningStyleEnum,qZ as LearningPathEntity,K4 as LearningJourneyFeature,V4 as LearningJourneyEvents,zZ as LearningGapEntity,KZ as LearnerProfileEntity,yj as LearnerModel,gZ as LearnerEntity,d as LearnerDashboardModel,MZ as LearnerAchievementEntity,Aj as LeaderboardPeriodEnum,CZ as LeaderboardEntryEntity,J as LEARNING_JOURNEY_OWNERS,LZ as HeartEntity,w0 as GetOnboardingProgressContract,l as GetLearnerDashboardInput,E0 as GetLearnerDashboardContract,n as GetDueCardsOutput,a as GetDueCardsInput,m0 as GetDueCardsContract,gj as EnrollmentStatusEnum,S as EnrollmentModel,bZ as EnrollmentEntity,aZ as EnrollmentCreatedEvent,E as EnrollInCourseInput,S0 as EnrollInCourseContract,h0 as DeckModel,FZ as DeckEntity,AZ as DailyGoalEntity,U0 as DailyGoalCompletedEvent,w as DEFAULT_XP_CONFIG,ZZ as DEFAULT_STREAK_CONFIG,jZ as DEFAULT_SRS_CONFIG,Wj as CourseStatusEnum,tZ as CoursePublishedEvent,QZ as CourseModuleEntity,C0 as CourseModel,DZ as CourseEntity,Qj as CourseDifficultyEnum,iZ as CourseCompletedEvent,Jj as ContentTypeEnum,t as CompleteLessonInput,r0 as CompleteLessonContract,M0 as CertificateIssuedEvent,pZ as CertificateEntity,UZ as CardScheduleEntity,I0 as CardReviewedEvent,kZ as CardReviewEntity,kj as CardRatingEnum,Sj as CardModel,XZ as CardEntity,Tj as AttemptStatusEnum,X0 as AchievementUnlockedEvent,Pj as AchievementTypeEnum,rj as AchievementModel,GZ as AchievementEntity};
|