@telebort/question-banks 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -78,6 +78,10 @@ var QuestionMetadataSchema = z.object({
78
78
  // seconds
79
79
  bloomsTaxonomy: BloomsTaxonomySchema,
80
80
  tags: z.array(z.string()),
81
+ conceptTags: z.array(z.string()).optional(),
82
+ // mastery-relevant concept tags
83
+ metaTags: z.array(z.string()).optional(),
84
+ // Bloom's types, course codes, internal
81
85
  source: z.string().default("exit-ticket"),
82
86
  version: z.string().default("1.1"),
83
87
  createdDate: z.string().optional(),
@@ -120,70 +124,17 @@ QuestionSchema.omit({
120
124
  })
121
125
  ).length(4)
122
126
  });
123
- var CourseDomainSchema = z.enum([
124
- // AI & Data Science
125
- "ai_data_science",
126
- "ai_ml_cv",
127
- "ai_generative",
128
- "ai_advanced",
129
- "ai",
130
- // Prompt Engineering (general AI)
131
- // Web Development
132
- "web_development",
133
- // Mobile Development
134
- "mobile_development",
135
- "mobile",
136
- // Block-based Programming
137
- "block_based",
138
- "python_programming",
139
- "design",
140
- "artificial_intelligence",
141
- // Block-based AI courses (BBAI)
142
- // Computer Science
143
- "cs_foundations",
144
- // CS1-CS4 courses
145
- // Foundation
146
- "foundation",
147
- "creative_computing",
148
- // Utility / Tools
149
- "tools"
150
- // IDE Setup, Git, Vibe Coding
151
- ]);
152
- var CourseTierSchema = z.enum([
153
- "beginner",
154
- // Block-based courses (BBW, BBP, BBD, BBAI)
155
- "foundation",
156
- "intermediate",
157
- "advanced"
158
- ]);
159
- var LessonSchema = z.object({
160
- lessonId: z.string(),
161
- // e.g., "ai-1-lesson-1"
162
- lessonNumber: z.number().int().positive(),
163
- lessonTitle: z.string(),
164
- lessonSlug: z.string().optional(),
165
- totalQuestions: z.number().int().positive().default(5),
166
- questions: z.array(QuestionSchema)
167
- });
168
- var CourseSchema = z.object({
169
- courseId: z.string(),
170
- // e.g., "ai-1"
171
- courseName: z.string(),
172
- // e.g., "AI-1 Data Analysis and Data Science"
173
- courseCode: z.string(),
174
- // e.g., "AI1"
175
- domain: CourseDomainSchema,
176
- tier: CourseTierSchema,
177
- difficulty: z.number().int().min(1).max(5),
178
- totalLessons: z.number().int().positive(),
179
- totalQuestions: z.number().int().positive(),
180
- sourceFile: z.string().optional(),
181
- lessons: z.array(LessonSchema)
182
- });
183
- CourseSchema.omit({ lessons: true }).extend({
184
- lessons: z.array(
185
- LessonSchema.omit({ questions: true })
186
- ).optional()
127
+ var AssessmentBlueprintSchema = z.object({
128
+ assessmentId: z.string(),
129
+ // e.g., "cs1-quiz-1"
130
+ title: z.string(),
131
+ // e.g., "Quiz 1"
132
+ description: z.string(),
133
+ lessonRange: z.tuple([z.number().int().positive(), z.number().int().positive()]).refine(([start2, end2]) => start2 <= end2, { message: "lessonRange start must be <= end" }),
134
+ questionCount: z.number().int().positive(),
135
+ passingScore: z.number().min(0).max(100),
136
+ timeLimitMinutes: z.number().int().positive().nullable(),
137
+ tags: z.array(z.string()).optional()
187
138
  });
188
139
  var UserResponseSchema = z.object({
189
140
  questionId: z.string(),
@@ -224,7 +175,7 @@ var MisconceptionReportSchema = z.object({
224
175
  description: z.string().optional()
225
176
  });
226
177
  z.object({
227
- assessmentId: z.string().uuid(),
178
+ assessmentId: z.string(),
228
179
  userId: z.string().optional(),
229
180
  courseId: z.string(),
230
181
  lessonId: z.string().optional(),
@@ -260,6 +211,74 @@ z.object({
260
211
  questionCount: z.number().int(),
261
212
  answeredCount: z.number().int()
262
213
  });
214
+
215
+ // src/schemas/course.ts
216
+ var CourseDomainSchema = z.enum([
217
+ // AI & Data Science
218
+ "ai_data_science",
219
+ "ai_ml_cv",
220
+ "ai_generative",
221
+ "ai_advanced",
222
+ "ai",
223
+ // Prompt Engineering (general AI)
224
+ // Web Development
225
+ "web_development",
226
+ // Mobile Development
227
+ "mobile_development",
228
+ "mobile",
229
+ // Block-based Programming
230
+ "block_based",
231
+ "python_programming",
232
+ "design",
233
+ "artificial_intelligence",
234
+ // Block-based AI courses (BBAI)
235
+ // Computer Science
236
+ "cs_foundations",
237
+ // CS1-CS4 courses
238
+ // Foundation
239
+ "foundation",
240
+ "creative_computing",
241
+ // Utility / Tools
242
+ "tools"
243
+ // IDE Setup, Git, Vibe Coding
244
+ ]);
245
+ var CourseTierSchema = z.enum([
246
+ "beginner",
247
+ // Block-based courses (BBW, BBP, BBD, BBAI)
248
+ "foundation",
249
+ "intermediate",
250
+ "advanced"
251
+ ]);
252
+ var LessonSchema = z.object({
253
+ lessonId: z.string(),
254
+ // e.g., "ai-1-lesson-1"
255
+ lessonNumber: z.number().int().positive(),
256
+ lessonTitle: z.string(),
257
+ lessonSlug: z.string().optional(),
258
+ totalQuestions: z.number().int().positive().default(5),
259
+ questions: z.array(QuestionSchema)
260
+ });
261
+ var CourseSchema = z.object({
262
+ courseId: z.string(),
263
+ // e.g., "ai-1"
264
+ courseName: z.string(),
265
+ // e.g., "AI-1 Data Analysis and Data Science"
266
+ courseCode: z.string(),
267
+ // e.g., "AI1"
268
+ domain: CourseDomainSchema,
269
+ tier: CourseTierSchema,
270
+ difficulty: z.number().int().min(1).max(5),
271
+ totalLessons: z.number().int().positive(),
272
+ totalQuestions: z.number().int().positive(),
273
+ sourceFile: z.string().optional(),
274
+ lessons: z.array(LessonSchema),
275
+ assessments: z.array(AssessmentBlueprintSchema).optional()
276
+ });
277
+ CourseSchema.omit({ lessons: true }).extend({
278
+ lessons: z.array(
279
+ LessonSchema.omit({ questions: true })
280
+ ).optional()
281
+ });
263
282
  var CertificationProjectSchema = z.object({
264
283
  projectNumber: z.number().int().positive(),
265
284
  projectName: z.string(),
@@ -547,6 +566,138 @@ ${green(bold("Valid!"))} File passed schema validation.
547
566
  }
548
567
  }
549
568
 
569
+ // src/core/assessment.ts
570
+ var SeededRandom = class {
571
+ constructor(seed) {
572
+ this.state = seed;
573
+ }
574
+ next() {
575
+ this.state = (this.state * 1103515245 + 12345) % 2147483648;
576
+ return this.state / 2147483648;
577
+ }
578
+ /** Fisher-Yates shuffle */
579
+ shuffle(array) {
580
+ const result = [...array];
581
+ for (let i3 = result.length - 1; i3 > 0; i3--) {
582
+ const j2 = Math.floor(this.next() * (i3 + 1));
583
+ const temp = result[i3];
584
+ result[i3] = result[j2];
585
+ result[j2] = temp;
586
+ }
587
+ return result;
588
+ }
589
+ };
590
+ function hashString(str) {
591
+ let hash = 0;
592
+ for (let i3 = 0; i3 < str.length; i3++) {
593
+ const char = str.charCodeAt(i3);
594
+ hash = (hash << 5) - hash + char;
595
+ hash = hash & hash;
596
+ }
597
+ return Math.abs(hash);
598
+ }
599
+ function createAssessmentModule(getCourse) {
600
+ return {
601
+ async getAssessments(courseId) {
602
+ const course = await getCourse(courseId);
603
+ return course?.assessments ?? [];
604
+ },
605
+ async buildAssessmentQuiz(courseId, assessmentId, seed) {
606
+ const course = await getCourse(courseId);
607
+ if (!course) throw new Error(`Course not found: ${courseId}`);
608
+ const blueprint = course.assessments?.find((a2) => a2.assessmentId === assessmentId);
609
+ if (!blueprint) throw new Error(`Assessment not found: ${assessmentId}`);
610
+ const [rangeStart, rangeEnd] = blueprint.lessonRange;
611
+ const pool = [];
612
+ for (const lesson of course.lessons) {
613
+ if (lesson.lessonNumber >= rangeStart && lesson.lessonNumber <= rangeEnd) {
614
+ pool.push(...lesson.questions);
615
+ }
616
+ }
617
+ if (pool.length === 0) {
618
+ throw new Error(`No questions found for lessons ${rangeStart}-${rangeEnd} in ${courseId}`);
619
+ }
620
+ const seedValue = seed ? hashString(seed) : Date.now();
621
+ const rng = new SeededRandom(seedValue);
622
+ const shuffled = rng.shuffle(pool);
623
+ return shuffled.slice(0, Math.min(blueprint.questionCount, shuffled.length));
624
+ },
625
+ async gradeAssessment(courseId, assessmentId, responses, passingScore) {
626
+ const course = await getCourse(courseId);
627
+ if (!course) throw new Error(`Course not found: ${courseId}`);
628
+ const blueprint = course.assessments?.find((a2) => a2.assessmentId === assessmentId);
629
+ if (!blueprint) throw new Error(`Assessment not found: ${assessmentId}`);
630
+ const threshold = passingScore ?? blueprint.passingScore;
631
+ const questionMap = /* @__PURE__ */ new Map();
632
+ for (const lesson of course.lessons) {
633
+ for (const q of lesson.questions) {
634
+ questionMap.set(q.questionId, q);
635
+ }
636
+ }
637
+ const gradedResponses = [];
638
+ let correctCount = 0;
639
+ for (const response of responses) {
640
+ const question = questionMap.get(response.questionId);
641
+ if (!question) continue;
642
+ const selectedOption = question.options.find((o2) => o2.key === response.selectedAnswer);
643
+ const isCorrect = selectedOption?.isCorrect ?? false;
644
+ if (isCorrect) correctCount++;
645
+ gradedResponses.push({
646
+ questionId: response.questionId,
647
+ selectedAnswer: response.selectedAnswer,
648
+ correctAnswer: question.correctAnswer,
649
+ isCorrect,
650
+ misconceptionId: isCorrect ? null : selectedOption?.misconceptionId ?? null,
651
+ feedback: selectedOption?.feedback,
652
+ timeSpent: response.timeSpent
653
+ });
654
+ }
655
+ const totalQuestions = gradedResponses.length;
656
+ const score = totalQuestions > 0 ? Math.round(correctCount / totalQuestions * 100) : 0;
657
+ const misconceptionCounts = /* @__PURE__ */ new Map();
658
+ for (const gr of gradedResponses) {
659
+ if (gr.misconceptionId) {
660
+ const existing = misconceptionCounts.get(gr.misconceptionId);
661
+ if (existing) {
662
+ existing.count++;
663
+ existing.questionIds.push(gr.questionId);
664
+ } else {
665
+ misconceptionCounts.set(gr.misconceptionId, {
666
+ count: 1,
667
+ questionIds: [gr.questionId]
668
+ });
669
+ }
670
+ }
671
+ }
672
+ const misconceptions = Array.from(misconceptionCounts.entries()).map(([id, data]) => ({
673
+ misconceptionId: id,
674
+ count: data.count,
675
+ percentage: Math.round(data.count / totalQuestions * 100),
676
+ questionIds: data.questionIds
677
+ })).sort((a2, b) => b.count - a2.count);
678
+ if (totalQuestions === 0) {
679
+ throw new Error(`No valid questions matched for assessment ${assessmentId}. All ${responses.length} response(s) referenced unknown questionIds.`);
680
+ }
681
+ const now = (/* @__PURE__ */ new Date()).toISOString();
682
+ const generatedId = `grade-${courseId}-${assessmentId}-${hashString(now).toString(36)}`;
683
+ return {
684
+ assessmentId: generatedId,
685
+ courseId,
686
+ totalQuestions,
687
+ correctCount,
688
+ incorrectCount: totalQuestions - correctCount,
689
+ score,
690
+ passed: score >= threshold,
691
+ responses: gradedResponses,
692
+ misconceptions,
693
+ topMisconceptions: misconceptions.slice(0, 3).map((m3) => m3.misconceptionId),
694
+ submittedAt: now,
695
+ gradedAt: now
696
+ };
697
+ }
698
+ };
699
+ }
700
+
550
701
  // src/knowledge/index.ts
551
702
  function createLogger(config) {
552
703
  return {
@@ -23288,6 +23439,7 @@ function createExitTicketsSDK(config) {
23288
23439
  };
23289
23440
  const malaysiaMath = createMalaysiaMathModule(malaysiaMathConfig);
23290
23441
  const malaysiaMathTaxonomy = createMalaysiaMathTaxonomyModule(malaysiaMathConfig);
23442
+ const assessment = createAssessmentModule(data.getCourse);
23291
23443
  return {
23292
23444
  data,
23293
23445
  query,
@@ -23298,7 +23450,8 @@ function createExitTicketsSDK(config) {
23298
23450
  knowledge,
23299
23451
  taxonomy,
23300
23452
  malaysiaMath,
23301
- malaysiaMathTaxonomy
23453
+ malaysiaMathTaxonomy,
23454
+ assessment
23302
23455
  };
23303
23456
  }
23304
23457
 
@@ -23467,26 +23620,32 @@ async function loadCourseData(filePath) {
23467
23620
  }
23468
23621
  function extractQuestions(courseData) {
23469
23622
  const questions = [];
23623
+ const pushQuestion = (q) => {
23624
+ questions.push({
23625
+ questionId: q.questionId || q.globalId || `${courseData.courseId || "unknown"}-unknown`,
23626
+ prompt: q.prompt || "",
23627
+ options: (q.options || []).map((o2) => ({
23628
+ key: o2.key || "",
23629
+ text: o2.text || "",
23630
+ isCorrect: o2.isCorrect || false
23631
+ })),
23632
+ hasCodeBlock: q.hasCodeBlock || false,
23633
+ codeLanguage: q.codeLanguage || null,
23634
+ codeContent: q.codeContent || null
23635
+ });
23636
+ };
23470
23637
  if (courseData.lessons) {
23471
23638
  for (const lesson of courseData.lessons) {
23472
23639
  if (lesson.questions) {
23473
23640
  for (const q of lesson.questions) {
23474
- questions.push({
23475
- questionId: q.questionId || `${courseData.courseId}-unknown`,
23476
- prompt: q.prompt || "",
23477
- options: (q.options || []).map((o2) => ({
23478
- key: o2.key || "",
23479
- text: o2.text || "",
23480
- isCorrect: o2.isCorrect || false
23481
- })),
23482
- // CS-specific fields for enhanced analysis
23483
- hasCodeBlock: q.hasCodeBlock || false,
23484
- codeLanguage: q.codeLanguage || null,
23485
- codeContent: q.codeContent || null
23486
- });
23641
+ pushQuestion(q);
23487
23642
  }
23488
23643
  }
23489
23644
  }
23645
+ } else if (courseData.questions && Array.isArray(courseData.questions)) {
23646
+ for (const q of courseData.questions) {
23647
+ pushQuestion(q);
23648
+ }
23490
23649
  }
23491
23650
  return questions;
23492
23651
  }