@telebort/question-banks 1.0.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.
@@ -0,0 +1,220 @@
1
+ import { z } from 'zod';
2
+
3
+ // src/schemas/question.ts
4
+ var FeedbackSchema = z.object({
5
+ short: z.string().min(1).max(200),
6
+ detailed: z.string().min(1).max(1e3),
7
+ socraticHint: z.string().max(300).optional()
8
+ });
9
+ var OptionKeySchema = z.enum(["A", "B", "C", "D"]);
10
+ var OptionSchema = z.object({
11
+ key: OptionKeySchema,
12
+ text: z.string().min(1),
13
+ isCorrect: z.boolean(),
14
+ // v1.1 fields - optional for backward compatibility
15
+ misconceptionId: z.string().optional(),
16
+ feedback: FeedbackSchema.optional()
17
+ });
18
+ var QuestionTypeSchema = z.enum([
19
+ "vocabulary",
20
+ "code_understanding",
21
+ "problem_solving",
22
+ "application",
23
+ "reflection"
24
+ ]);
25
+ var QuestionArchetypeSchema = z.enum([
26
+ "vocabulary",
27
+ "trace",
28
+ "bebras",
29
+ "blockmodel",
30
+ "parsons"
31
+ ]);
32
+ var DifficultySchema = z.enum(["easy", "medium", "hard", "challenge"]);
33
+ var BloomsTaxonomySchema = z.enum([
34
+ "remember",
35
+ "understand",
36
+ "apply",
37
+ "analyze",
38
+ "evaluate",
39
+ "create"
40
+ ]);
41
+ var QuestionMetadataSchema = z.object({
42
+ difficulty: DifficultySchema,
43
+ estimatedTime: z.number().int().positive(),
44
+ // seconds
45
+ bloomsTaxonomy: BloomsTaxonomySchema,
46
+ tags: z.array(z.string()),
47
+ source: z.string().default("exit-ticket"),
48
+ version: z.string().default("1.1"),
49
+ createdDate: z.string().optional(),
50
+ lastModified: z.string().optional()
51
+ });
52
+ var QuestionSchema = z.object({
53
+ // Identifiers
54
+ questionId: z.string().regex(/^[a-z0-9-]+-l\d+-q\d+$/),
55
+ globalId: z.string().regex(/^exit-ticket-\d{4}$/),
56
+ questionNumber: z.number().int().positive().max(5),
57
+ // Classification
58
+ questionType: QuestionTypeSchema,
59
+ questionTypeLabel: z.string(),
60
+ questionArchetype: QuestionArchetypeSchema.optional(),
61
+ // v1.1
62
+ // Content
63
+ prompt: z.string().min(10),
64
+ hasCodeBlock: z.boolean(),
65
+ codeLanguage: z.string().nullable(),
66
+ codeContent: z.string().nullable(),
67
+ // Misconception targeting (v1.1)
68
+ misconceptionTargets: z.array(z.string()).optional(),
69
+ // Answer options (exactly 4)
70
+ options: z.array(OptionSchema).length(4),
71
+ // Correct answer
72
+ correctAnswer: OptionKeySchema,
73
+ correctAnswerText: z.string(),
74
+ // Metadata
75
+ metadata: QuestionMetadataSchema
76
+ });
77
+ var QuestionWithoutAnswerSchema = QuestionSchema.omit({
78
+ correctAnswer: true,
79
+ correctAnswerText: true
80
+ }).extend({
81
+ options: z.array(
82
+ OptionSchema.omit({
83
+ isCorrect: true,
84
+ feedback: true,
85
+ misconceptionId: true
86
+ })
87
+ ).length(4)
88
+ });
89
+ var CourseDomainSchema = z.enum([
90
+ // AI & Data Science
91
+ "ai_data_science",
92
+ "ai_ml_cv",
93
+ "ai_generative",
94
+ "ai_advanced",
95
+ // Web Development
96
+ "web_development",
97
+ // Mobile Development
98
+ "mobile_development",
99
+ "mobile",
100
+ // Block-based Programming
101
+ "block_based",
102
+ "python_programming",
103
+ "design",
104
+ // Foundation
105
+ "foundation",
106
+ "creative_computing"
107
+ ]);
108
+ var CourseTierSchema = z.enum([
109
+ "foundation",
110
+ "intermediate",
111
+ "advanced"
112
+ ]);
113
+ var LessonSchema = z.object({
114
+ lessonId: z.string(),
115
+ // e.g., "ai-1-lesson-1"
116
+ lessonNumber: z.number().int().positive(),
117
+ lessonTitle: z.string(),
118
+ lessonSlug: z.string().optional(),
119
+ totalQuestions: z.number().int().positive().default(5),
120
+ questions: z.array(QuestionSchema)
121
+ });
122
+ var CourseSchema = z.object({
123
+ courseId: z.string(),
124
+ // e.g., "ai-1"
125
+ courseName: z.string(),
126
+ // e.g., "AI-1 Data Analysis and Data Science"
127
+ courseCode: z.string(),
128
+ // e.g., "AI1"
129
+ domain: CourseDomainSchema,
130
+ tier: CourseTierSchema,
131
+ difficulty: z.number().int().min(1).max(5),
132
+ totalLessons: z.number().int().positive(),
133
+ totalQuestions: z.number().int().positive(),
134
+ sourceFile: z.string().optional(),
135
+ lessons: z.array(LessonSchema)
136
+ });
137
+ var CourseSummarySchema = CourseSchema.omit({ lessons: true }).extend({
138
+ lessons: z.array(
139
+ LessonSchema.omit({ questions: true })
140
+ ).optional()
141
+ });
142
+ var UserResponseSchema = z.object({
143
+ questionId: z.string(),
144
+ selectedAnswer: OptionKeySchema,
145
+ timeSpent: z.number().int().nonnegative().optional(),
146
+ // milliseconds
147
+ submittedAt: z.string().datetime().optional()
148
+ });
149
+ var AssessmentSubmissionSchema = z.object({
150
+ assessmentId: z.string().uuid().optional(),
151
+ // Generated if not provided
152
+ userId: z.string().optional(),
153
+ // For authenticated users
154
+ sessionId: z.string().optional(),
155
+ // For anonymous sessions
156
+ courseId: z.string(),
157
+ lessonId: z.string().optional(),
158
+ responses: z.array(UserResponseSchema).min(1),
159
+ submittedAt: z.string().datetime(),
160
+ metadata: z.record(z.unknown()).optional()
161
+ // Custom metadata
162
+ });
163
+ var GradedResponseSchema = z.object({
164
+ questionId: z.string(),
165
+ selectedAnswer: OptionKeySchema,
166
+ correctAnswer: OptionKeySchema,
167
+ isCorrect: z.boolean(),
168
+ misconceptionId: z.string().nullable(),
169
+ // For incorrect answers
170
+ feedback: FeedbackSchema.optional(),
171
+ timeSpent: z.number().int().nonnegative().optional()
172
+ });
173
+ var MisconceptionReportSchema = z.object({
174
+ misconceptionId: z.string(),
175
+ count: z.number().int().positive(),
176
+ percentage: z.number().min(0).max(100),
177
+ questionIds: z.array(z.string()),
178
+ description: z.string().optional()
179
+ });
180
+ var GradedAssessmentSchema = z.object({
181
+ assessmentId: z.string().uuid(),
182
+ userId: z.string().optional(),
183
+ courseId: z.string(),
184
+ lessonId: z.string().optional(),
185
+ // Scores
186
+ totalQuestions: z.number().int().positive(),
187
+ correctCount: z.number().int().nonnegative(),
188
+ incorrectCount: z.number().int().nonnegative(),
189
+ score: z.number().min(0).max(100),
190
+ // Percentage
191
+ passed: z.boolean(),
192
+ // Based on threshold (default 70%)
193
+ // Detailed results
194
+ responses: z.array(GradedResponseSchema),
195
+ // Misconception analysis (v1.1 feature)
196
+ misconceptions: z.array(MisconceptionReportSchema).optional(),
197
+ topMisconceptions: z.array(z.string()).max(3).optional(),
198
+ // Timing
199
+ totalTimeSpent: z.number().int().nonnegative().optional(),
200
+ // milliseconds
201
+ averageTimePerQuestion: z.number().nonnegative().optional(),
202
+ submittedAt: z.string().datetime(),
203
+ gradedAt: z.string().datetime()
204
+ });
205
+ var ValidationResultSchema = z.object({
206
+ valid: z.boolean(),
207
+ errors: z.array(z.object({
208
+ path: z.array(z.union([z.string(), z.number()])),
209
+ message: z.string(),
210
+ code: z.string().optional()
211
+ })).optional(),
212
+ isComplete: z.boolean(),
213
+ // All questions answered
214
+ questionCount: z.number().int(),
215
+ answeredCount: z.number().int()
216
+ });
217
+
218
+ export { AssessmentSubmissionSchema, BloomsTaxonomySchema, CourseDomainSchema, CourseSchema, CourseSummarySchema, CourseTierSchema, DifficultySchema, FeedbackSchema, GradedAssessmentSchema, GradedResponseSchema, LessonSchema, MisconceptionReportSchema, OptionKeySchema, OptionSchema, QuestionArchetypeSchema, QuestionMetadataSchema, QuestionSchema, QuestionTypeSchema, QuestionWithoutAnswerSchema, UserResponseSchema, ValidationResultSchema };
219
+ //# sourceMappingURL=index.js.map
220
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/schemas/question.ts","../../src/schemas/course.ts","../../src/schemas/assessment.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["import { z } from 'zod'\n\n// ============================================================================\n// Feedback Schema (v1.1 enhancement)\n// ============================================================================\n\n/**\n * Structured feedback following the \"Not Yet\" protocol\n * - short: Brief acknowledgment (1 sentence)\n * - detailed: Full explanation of the misconception\n * - socraticHint: Optional guiding question for discovery learning\n */\nexport const FeedbackSchema = z.object({\n short: z.string().min(1).max(200),\n detailed: z.string().min(1).max(1000),\n socraticHint: z.string().max(300).optional(),\n})\n\nexport type Feedback = z.infer<typeof FeedbackSchema>\n\n// ============================================================================\n// Option Schema\n// ============================================================================\n\nexport const OptionKeySchema = z.enum(['A', 'B', 'C', 'D'])\n\nexport type OptionKey = z.infer<typeof OptionKeySchema>\n\n/**\n * Answer option with optional misconception tracking (v1.1)\n */\nexport const OptionSchema = z.object({\n key: OptionKeySchema,\n text: z.string().min(1),\n isCorrect: z.boolean(),\n // v1.1 fields - optional for backward compatibility\n misconceptionId: z.string().optional(),\n feedback: FeedbackSchema.optional(),\n})\n\nexport type Option = z.infer<typeof OptionSchema>\n\n// ============================================================================\n// Question Type Enums\n// ============================================================================\n\nexport const QuestionTypeSchema = z.enum([\n 'vocabulary',\n 'code_understanding',\n 'problem_solving',\n 'application',\n 'reflection',\n])\n\nexport type QuestionType = z.infer<typeof QuestionTypeSchema>\n\n/**\n * Question archetype for pedagogical classification\n * - vocabulary: Definition/terminology questions\n * - trace: Code tracing (mental execution)\n * - bebras: Logic-first, computational thinking\n * - blockmodel: Surface/flow/purpose analysis\n * - parsons: Code block reordering (future)\n */\nexport const QuestionArchetypeSchema = z.enum([\n 'vocabulary',\n 'trace',\n 'bebras',\n 'blockmodel',\n 'parsons',\n])\n\nexport type QuestionArchetype = z.infer<typeof QuestionArchetypeSchema>\n\nexport const DifficultySchema = z.enum(['easy', 'medium', 'hard', 'challenge'])\n\nexport type Difficulty = z.infer<typeof DifficultySchema>\n\nexport const BloomsTaxonomySchema = z.enum([\n 'remember',\n 'understand',\n 'apply',\n 'analyze',\n 'evaluate',\n 'create',\n])\n\nexport type BloomsTaxonomy = z.infer<typeof BloomsTaxonomySchema>\n\n// ============================================================================\n// Question Metadata Schema\n// ============================================================================\n\nexport const QuestionMetadataSchema = z.object({\n difficulty: DifficultySchema,\n estimatedTime: z.number().int().positive(), // seconds\n bloomsTaxonomy: BloomsTaxonomySchema,\n tags: z.array(z.string()),\n source: z.string().default('exit-ticket'),\n version: z.string().default('1.1'),\n createdDate: z.string().optional(),\n lastModified: z.string().optional(),\n})\n\nexport type QuestionMetadata = z.infer<typeof QuestionMetadataSchema>\n\n// ============================================================================\n// Question Schema (v1.1)\n// ============================================================================\n\n/**\n * Full question schema with v1.1 enhancements:\n * - questionArchetype: Pedagogical classification\n * - misconceptionTargets: Expected misconceptions this question probes\n * - options with misconceptionId and structured feedback\n */\nexport const QuestionSchema = z.object({\n // Identifiers\n questionId: z.string().regex(/^[a-z0-9-]+-l\\d+-q\\d+$/),\n globalId: z.string().regex(/^exit-ticket-\\d{4}$/),\n questionNumber: z.number().int().positive().max(5),\n\n // Classification\n questionType: QuestionTypeSchema,\n questionTypeLabel: z.string(),\n questionArchetype: QuestionArchetypeSchema.optional(), // v1.1\n\n // Content\n prompt: z.string().min(10),\n hasCodeBlock: z.boolean(),\n codeLanguage: z.string().nullable(),\n codeContent: z.string().nullable(),\n\n // Misconception targeting (v1.1)\n misconceptionTargets: z.array(z.string()).optional(),\n\n // Answer options (exactly 4)\n options: z.array(OptionSchema).length(4),\n\n // Correct answer\n correctAnswer: OptionKeySchema,\n correctAnswerText: z.string(),\n\n // Metadata\n metadata: QuestionMetadataSchema,\n})\n\nexport type Question = z.infer<typeof QuestionSchema>\n\n// ============================================================================\n// Question without answer (for Standard tier - no answer leakage)\n// ============================================================================\n\nexport const QuestionWithoutAnswerSchema = QuestionSchema.omit({\n correctAnswer: true,\n correctAnswerText: true,\n}).extend({\n options: z.array(\n OptionSchema.omit({\n isCorrect: true,\n feedback: true,\n misconceptionId: true,\n })\n ).length(4),\n})\n\nexport type QuestionWithoutAnswer = z.infer<typeof QuestionWithoutAnswerSchema>\n","import { z } from 'zod'\nimport { QuestionSchema } from './question'\n\n// ============================================================================\n// Course Domain & Tier\n// ============================================================================\n\nexport const CourseDomainSchema = z.enum([\n // AI & Data Science\n 'ai_data_science',\n 'ai_ml_cv',\n 'ai_generative',\n 'ai_advanced',\n // Web Development\n 'web_development',\n // Mobile Development\n 'mobile_development',\n 'mobile',\n // Block-based Programming\n 'block_based',\n 'python_programming',\n 'design',\n // Foundation\n 'foundation',\n 'creative_computing',\n])\n\nexport type CourseDomain = z.infer<typeof CourseDomainSchema>\n\nexport const CourseTierSchema = z.enum([\n 'foundation',\n 'intermediate',\n 'advanced',\n])\n\nexport type CourseTier = z.infer<typeof CourseTierSchema>\n\n// ============================================================================\n// Lesson Schema\n// ============================================================================\n\nexport const LessonSchema = z.object({\n lessonId: z.string(), // e.g., \"ai-1-lesson-1\"\n lessonNumber: z.number().int().positive(),\n lessonTitle: z.string(),\n lessonSlug: z.string().optional(),\n totalQuestions: z.number().int().positive().default(5),\n questions: z.array(QuestionSchema),\n})\n\nexport type Lesson = z.infer<typeof LessonSchema>\n\n// ============================================================================\n// Course Schema\n// ============================================================================\n\nexport const CourseSchema = z.object({\n courseId: z.string(), // e.g., \"ai-1\"\n courseName: z.string(), // e.g., \"AI-1 Data Analysis and Data Science\"\n courseCode: z.string(), // e.g., \"AI1\"\n domain: CourseDomainSchema,\n tier: CourseTierSchema,\n difficulty: z.number().int().min(1).max(5),\n totalLessons: z.number().int().positive(),\n totalQuestions: z.number().int().positive(),\n sourceFile: z.string().optional(),\n lessons: z.array(LessonSchema),\n})\n\nexport type Course = z.infer<typeof CourseSchema>\n\n// ============================================================================\n// Course Summary (without questions - for listing)\n// ============================================================================\n\nexport const CourseSummarySchema = CourseSchema.omit({ lessons: true }).extend({\n lessons: z.array(\n LessonSchema.omit({ questions: true })\n ).optional(),\n})\n\nexport type CourseSummary = z.infer<typeof CourseSummarySchema>\n","import { z } from 'zod'\nimport { OptionKeySchema, FeedbackSchema } from './question'\n\n// ============================================================================\n// User Response Schema\n// ============================================================================\n\n/**\n * A single user response to a question\n */\nexport const UserResponseSchema = z.object({\n questionId: z.string(),\n selectedAnswer: OptionKeySchema,\n timeSpent: z.number().int().nonnegative().optional(), // milliseconds\n submittedAt: z.string().datetime().optional(),\n})\n\nexport type UserResponse = z.infer<typeof UserResponseSchema>\n\n// ============================================================================\n// Assessment Submission Schema\n// ============================================================================\n\n/**\n * An assessment submission containing multiple responses\n */\nexport const AssessmentSubmissionSchema = z.object({\n assessmentId: z.string().uuid().optional(), // Generated if not provided\n userId: z.string().optional(), // For authenticated users\n sessionId: z.string().optional(), // For anonymous sessions\n courseId: z.string(),\n lessonId: z.string().optional(),\n responses: z.array(UserResponseSchema).min(1),\n submittedAt: z.string().datetime(),\n metadata: z.record(z.unknown()).optional(), // Custom metadata\n})\n\nexport type AssessmentSubmission = z.infer<typeof AssessmentSubmissionSchema>\n\n// ============================================================================\n// Graded Response Schema (Premium tier)\n// ============================================================================\n\n/**\n * Result of grading a single response\n */\nexport const GradedResponseSchema = z.object({\n questionId: z.string(),\n selectedAnswer: OptionKeySchema,\n correctAnswer: OptionKeySchema,\n isCorrect: z.boolean(),\n misconceptionId: z.string().nullable(), // For incorrect answers\n feedback: FeedbackSchema.optional(),\n timeSpent: z.number().int().nonnegative().optional(),\n})\n\nexport type GradedResponse = z.infer<typeof GradedResponseSchema>\n\n// ============================================================================\n// Misconception Report Schema (Premium tier)\n// ============================================================================\n\n/**\n * Aggregated misconception analysis for an assessment\n */\nexport const MisconceptionReportSchema = z.object({\n misconceptionId: z.string(),\n count: z.number().int().positive(),\n percentage: z.number().min(0).max(100),\n questionIds: z.array(z.string()),\n description: z.string().optional(),\n})\n\nexport type MisconceptionReport = z.infer<typeof MisconceptionReportSchema>\n\n// ============================================================================\n// Graded Assessment Schema (Premium tier)\n// ============================================================================\n\n/**\n * Complete graded assessment with scores and misconception analysis\n */\nexport const GradedAssessmentSchema = z.object({\n assessmentId: z.string().uuid(),\n userId: z.string().optional(),\n courseId: z.string(),\n lessonId: z.string().optional(),\n\n // Scores\n totalQuestions: z.number().int().positive(),\n correctCount: z.number().int().nonnegative(),\n incorrectCount: z.number().int().nonnegative(),\n score: z.number().min(0).max(100), // Percentage\n passed: z.boolean(), // Based on threshold (default 70%)\n\n // Detailed results\n responses: z.array(GradedResponseSchema),\n\n // Misconception analysis (v1.1 feature)\n misconceptions: z.array(MisconceptionReportSchema).optional(),\n topMisconceptions: z.array(z.string()).max(3).optional(),\n\n // Timing\n totalTimeSpent: z.number().int().nonnegative().optional(), // milliseconds\n averageTimePerQuestion: z.number().nonnegative().optional(),\n submittedAt: z.string().datetime(),\n gradedAt: z.string().datetime(),\n})\n\nexport type GradedAssessment = z.infer<typeof GradedAssessmentSchema>\n\n// ============================================================================\n// Validation Result Schema (Standard tier - no answer leakage)\n// ============================================================================\n\n/**\n * Result of validating an assessment submission\n * Does NOT reveal correct answers - only schema compliance\n */\nexport const ValidationResultSchema = z.object({\n valid: z.boolean(),\n errors: z.array(z.object({\n path: z.array(z.union([z.string(), z.number()])),\n message: z.string(),\n code: z.string().optional(),\n })).optional(),\n isComplete: z.boolean(), // All questions answered\n questionCount: z.number().int(),\n answeredCount: z.number().int(),\n})\n\nexport type ValidationResult = z.infer<typeof ValidationResultSchema>\n"]}
@@ -0,0 +1,306 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ // src/schemas/question.ts
6
+ var FeedbackSchema = zod.z.object({
7
+ short: zod.z.string().min(1).max(200),
8
+ detailed: zod.z.string().min(1).max(1e3),
9
+ socraticHint: zod.z.string().max(300).optional()
10
+ });
11
+ var OptionKeySchema = zod.z.enum(["A", "B", "C", "D"]);
12
+ var OptionSchema = zod.z.object({
13
+ key: OptionKeySchema,
14
+ text: zod.z.string().min(1),
15
+ isCorrect: zod.z.boolean(),
16
+ // v1.1 fields - optional for backward compatibility
17
+ misconceptionId: zod.z.string().optional(),
18
+ feedback: FeedbackSchema.optional()
19
+ });
20
+ var QuestionTypeSchema = zod.z.enum([
21
+ "vocabulary",
22
+ "code_understanding",
23
+ "problem_solving",
24
+ "application",
25
+ "reflection"
26
+ ]);
27
+ var QuestionArchetypeSchema = zod.z.enum([
28
+ "vocabulary",
29
+ "trace",
30
+ "bebras",
31
+ "blockmodel",
32
+ "parsons"
33
+ ]);
34
+ var DifficultySchema = zod.z.enum(["easy", "medium", "hard", "challenge"]);
35
+ var BloomsTaxonomySchema = zod.z.enum([
36
+ "remember",
37
+ "understand",
38
+ "apply",
39
+ "analyze",
40
+ "evaluate",
41
+ "create"
42
+ ]);
43
+ var QuestionMetadataSchema = zod.z.object({
44
+ difficulty: DifficultySchema,
45
+ estimatedTime: zod.z.number().int().positive(),
46
+ // seconds
47
+ bloomsTaxonomy: BloomsTaxonomySchema,
48
+ tags: zod.z.array(zod.z.string()),
49
+ source: zod.z.string().default("exit-ticket"),
50
+ version: zod.z.string().default("1.1"),
51
+ createdDate: zod.z.string().optional(),
52
+ lastModified: zod.z.string().optional()
53
+ });
54
+ var QuestionSchema = zod.z.object({
55
+ // Identifiers
56
+ questionId: zod.z.string().regex(/^[a-z0-9-]+-l\d+-q\d+$/),
57
+ globalId: zod.z.string().regex(/^exit-ticket-\d{4}$/),
58
+ questionNumber: zod.z.number().int().positive().max(5),
59
+ // Classification
60
+ questionType: QuestionTypeSchema,
61
+ questionTypeLabel: zod.z.string(),
62
+ questionArchetype: QuestionArchetypeSchema.optional(),
63
+ // v1.1
64
+ // Content
65
+ prompt: zod.z.string().min(10),
66
+ hasCodeBlock: zod.z.boolean(),
67
+ codeLanguage: zod.z.string().nullable(),
68
+ codeContent: zod.z.string().nullable(),
69
+ // Misconception targeting (v1.1)
70
+ misconceptionTargets: zod.z.array(zod.z.string()).optional(),
71
+ // Answer options (exactly 4)
72
+ options: zod.z.array(OptionSchema).length(4),
73
+ // Correct answer
74
+ correctAnswer: OptionKeySchema,
75
+ correctAnswerText: zod.z.string(),
76
+ // Metadata
77
+ metadata: QuestionMetadataSchema
78
+ });
79
+ QuestionSchema.omit({
80
+ correctAnswer: true,
81
+ correctAnswerText: true
82
+ }).extend({
83
+ options: zod.z.array(
84
+ OptionSchema.omit({
85
+ isCorrect: true,
86
+ feedback: true,
87
+ misconceptionId: true
88
+ })
89
+ ).length(4)
90
+ });
91
+ var CourseDomainSchema = zod.z.enum([
92
+ // AI & Data Science
93
+ "ai_data_science",
94
+ "ai_ml_cv",
95
+ "ai_generative",
96
+ "ai_advanced",
97
+ // Web Development
98
+ "web_development",
99
+ // Mobile Development
100
+ "mobile_development",
101
+ "mobile",
102
+ // Block-based Programming
103
+ "block_based",
104
+ "python_programming",
105
+ "design",
106
+ // Foundation
107
+ "foundation",
108
+ "creative_computing"
109
+ ]);
110
+ var CourseTierSchema = zod.z.enum([
111
+ "foundation",
112
+ "intermediate",
113
+ "advanced"
114
+ ]);
115
+ var LessonSchema = zod.z.object({
116
+ lessonId: zod.z.string(),
117
+ // e.g., "ai-1-lesson-1"
118
+ lessonNumber: zod.z.number().int().positive(),
119
+ lessonTitle: zod.z.string(),
120
+ lessonSlug: zod.z.string().optional(),
121
+ totalQuestions: zod.z.number().int().positive().default(5),
122
+ questions: zod.z.array(QuestionSchema)
123
+ });
124
+ var CourseSchema = zod.z.object({
125
+ courseId: zod.z.string(),
126
+ // e.g., "ai-1"
127
+ courseName: zod.z.string(),
128
+ // e.g., "AI-1 Data Analysis and Data Science"
129
+ courseCode: zod.z.string(),
130
+ // e.g., "AI1"
131
+ domain: CourseDomainSchema,
132
+ tier: CourseTierSchema,
133
+ difficulty: zod.z.number().int().min(1).max(5),
134
+ totalLessons: zod.z.number().int().positive(),
135
+ totalQuestions: zod.z.number().int().positive(),
136
+ sourceFile: zod.z.string().optional(),
137
+ lessons: zod.z.array(LessonSchema)
138
+ });
139
+ CourseSchema.omit({ lessons: true }).extend({
140
+ lessons: zod.z.array(
141
+ LessonSchema.omit({ questions: true })
142
+ ).optional()
143
+ });
144
+ var UserResponseSchema = zod.z.object({
145
+ questionId: zod.z.string(),
146
+ selectedAnswer: OptionKeySchema,
147
+ timeSpent: zod.z.number().int().nonnegative().optional(),
148
+ // milliseconds
149
+ submittedAt: zod.z.string().datetime().optional()
150
+ });
151
+ var AssessmentSubmissionSchema = zod.z.object({
152
+ assessmentId: zod.z.string().uuid().optional(),
153
+ // Generated if not provided
154
+ userId: zod.z.string().optional(),
155
+ // For authenticated users
156
+ sessionId: zod.z.string().optional(),
157
+ // For anonymous sessions
158
+ courseId: zod.z.string(),
159
+ lessonId: zod.z.string().optional(),
160
+ responses: zod.z.array(UserResponseSchema).min(1),
161
+ submittedAt: zod.z.string().datetime(),
162
+ metadata: zod.z.record(zod.z.unknown()).optional()
163
+ // Custom metadata
164
+ });
165
+ var GradedResponseSchema = zod.z.object({
166
+ questionId: zod.z.string(),
167
+ selectedAnswer: OptionKeySchema,
168
+ correctAnswer: OptionKeySchema,
169
+ isCorrect: zod.z.boolean(),
170
+ misconceptionId: zod.z.string().nullable(),
171
+ // For incorrect answers
172
+ feedback: FeedbackSchema.optional(),
173
+ timeSpent: zod.z.number().int().nonnegative().optional()
174
+ });
175
+ var MisconceptionReportSchema = zod.z.object({
176
+ misconceptionId: zod.z.string(),
177
+ count: zod.z.number().int().positive(),
178
+ percentage: zod.z.number().min(0).max(100),
179
+ questionIds: zod.z.array(zod.z.string()),
180
+ description: zod.z.string().optional()
181
+ });
182
+ zod.z.object({
183
+ assessmentId: zod.z.string().uuid(),
184
+ userId: zod.z.string().optional(),
185
+ courseId: zod.z.string(),
186
+ lessonId: zod.z.string().optional(),
187
+ // Scores
188
+ totalQuestions: zod.z.number().int().positive(),
189
+ correctCount: zod.z.number().int().nonnegative(),
190
+ incorrectCount: zod.z.number().int().nonnegative(),
191
+ score: zod.z.number().min(0).max(100),
192
+ // Percentage
193
+ passed: zod.z.boolean(),
194
+ // Based on threshold (default 70%)
195
+ // Detailed results
196
+ responses: zod.z.array(GradedResponseSchema),
197
+ // Misconception analysis (v1.1 feature)
198
+ misconceptions: zod.z.array(MisconceptionReportSchema).optional(),
199
+ topMisconceptions: zod.z.array(zod.z.string()).max(3).optional(),
200
+ // Timing
201
+ totalTimeSpent: zod.z.number().int().nonnegative().optional(),
202
+ // milliseconds
203
+ averageTimePerQuestion: zod.z.number().nonnegative().optional(),
204
+ submittedAt: zod.z.string().datetime(),
205
+ gradedAt: zod.z.string().datetime()
206
+ });
207
+ zod.z.object({
208
+ valid: zod.z.boolean(),
209
+ errors: zod.z.array(zod.z.object({
210
+ path: zod.z.array(zod.z.union([zod.z.string(), zod.z.number()])),
211
+ message: zod.z.string(),
212
+ code: zod.z.string().optional()
213
+ })).optional(),
214
+ isComplete: zod.z.boolean(),
215
+ // All questions answered
216
+ questionCount: zod.z.number().int(),
217
+ answeredCount: zod.z.number().int()
218
+ });
219
+
220
+ // src/validation/client.ts
221
+ var ValidationClient = class {
222
+ constructor(config) {
223
+ this.apiKey = config.apiKey;
224
+ this.baseUrl = config.baseUrl ?? "https://api.telebort.com/exit-tickets/v1";
225
+ this.timeout = config.timeout ?? 3e4;
226
+ }
227
+ /**
228
+ * Validate an assessment submission
229
+ *
230
+ * @param assessment - The assessment to validate
231
+ * @returns Validation result (no correct answers revealed)
232
+ */
233
+ async validateAssessment(assessment) {
234
+ const localResult = AssessmentSubmissionSchema.safeParse(assessment);
235
+ if (!localResult.success) {
236
+ return {
237
+ valid: false,
238
+ errors: localResult.error.issues.map((issue) => ({
239
+ path: issue.path,
240
+ message: issue.message,
241
+ code: issue.code
242
+ })),
243
+ isComplete: false,
244
+ questionCount: 0,
245
+ answeredCount: assessment.responses?.length ?? 0
246
+ };
247
+ }
248
+ if (this.apiKey && this.apiKey !== "offline") {
249
+ try {
250
+ const response = await fetch(`${this.baseUrl}/validate`, {
251
+ method: "POST",
252
+ headers: {
253
+ "Content-Type": "application/json",
254
+ "X-API-Key": this.apiKey
255
+ },
256
+ body: JSON.stringify(assessment),
257
+ signal: AbortSignal.timeout(this.timeout)
258
+ });
259
+ if (!response.ok) {
260
+ const error = await response.json();
261
+ throw new Error(`API Error: ${error.message}`);
262
+ }
263
+ return await response.json();
264
+ } catch (error) {
265
+ console.warn("API validation failed, using local validation:", error);
266
+ }
267
+ }
268
+ return {
269
+ valid: true,
270
+ isComplete: true,
271
+ // Assume complete for local validation
272
+ questionCount: assessment.responses.length,
273
+ answeredCount: assessment.responses.length
274
+ };
275
+ }
276
+ /**
277
+ * Validate assessment offline (no API call)
278
+ * Uses only Zod schemas
279
+ */
280
+ validateOffline(assessment) {
281
+ const result = AssessmentSubmissionSchema.safeParse(assessment);
282
+ if (!result.success) {
283
+ return {
284
+ valid: false,
285
+ errors: result.error.issues.map((issue) => ({
286
+ path: issue.path,
287
+ message: issue.message,
288
+ code: issue.code
289
+ })),
290
+ isComplete: false,
291
+ questionCount: 0,
292
+ answeredCount: 0
293
+ };
294
+ }
295
+ return {
296
+ valid: true,
297
+ isComplete: true,
298
+ questionCount: result.data.responses.length,
299
+ answeredCount: result.data.responses.length
300
+ };
301
+ }
302
+ };
303
+
304
+ exports.ValidationClient = ValidationClient;
305
+ //# sourceMappingURL=index.cjs.map
306
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/schemas/question.ts","../../src/schemas/course.ts","../../src/schemas/assessment.ts","../../src/validation/client.ts"],"names":["z"],"mappings":"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","file":"index.cjs","sourcesContent":["import { z } from 'zod'\n\n// ============================================================================\n// Feedback Schema (v1.1 enhancement)\n// ============================================================================\n\n/**\n * Structured feedback following the \"Not Yet\" protocol\n * - short: Brief acknowledgment (1 sentence)\n * - detailed: Full explanation of the misconception\n * - socraticHint: Optional guiding question for discovery learning\n */\nexport const FeedbackSchema = z.object({\n short: z.string().min(1).max(200),\n detailed: z.string().min(1).max(1000),\n socraticHint: z.string().max(300).optional(),\n})\n\nexport type Feedback = z.infer<typeof FeedbackSchema>\n\n// ============================================================================\n// Option Schema\n// ============================================================================\n\nexport const OptionKeySchema = z.enum(['A', 'B', 'C', 'D'])\n\nexport type OptionKey = z.infer<typeof OptionKeySchema>\n\n/**\n * Answer option with optional misconception tracking (v1.1)\n */\nexport const OptionSchema = z.object({\n key: OptionKeySchema,\n text: z.string().min(1),\n isCorrect: z.boolean(),\n // v1.1 fields - optional for backward compatibility\n misconceptionId: z.string().optional(),\n feedback: FeedbackSchema.optional(),\n})\n\nexport type Option = z.infer<typeof OptionSchema>\n\n// ============================================================================\n// Question Type Enums\n// ============================================================================\n\nexport const QuestionTypeSchema = z.enum([\n 'vocabulary',\n 'code_understanding',\n 'problem_solving',\n 'application',\n 'reflection',\n])\n\nexport type QuestionType = z.infer<typeof QuestionTypeSchema>\n\n/**\n * Question archetype for pedagogical classification\n * - vocabulary: Definition/terminology questions\n * - trace: Code tracing (mental execution)\n * - bebras: Logic-first, computational thinking\n * - blockmodel: Surface/flow/purpose analysis\n * - parsons: Code block reordering (future)\n */\nexport const QuestionArchetypeSchema = z.enum([\n 'vocabulary',\n 'trace',\n 'bebras',\n 'blockmodel',\n 'parsons',\n])\n\nexport type QuestionArchetype = z.infer<typeof QuestionArchetypeSchema>\n\nexport const DifficultySchema = z.enum(['easy', 'medium', 'hard', 'challenge'])\n\nexport type Difficulty = z.infer<typeof DifficultySchema>\n\nexport const BloomsTaxonomySchema = z.enum([\n 'remember',\n 'understand',\n 'apply',\n 'analyze',\n 'evaluate',\n 'create',\n])\n\nexport type BloomsTaxonomy = z.infer<typeof BloomsTaxonomySchema>\n\n// ============================================================================\n// Question Metadata Schema\n// ============================================================================\n\nexport const QuestionMetadataSchema = z.object({\n difficulty: DifficultySchema,\n estimatedTime: z.number().int().positive(), // seconds\n bloomsTaxonomy: BloomsTaxonomySchema,\n tags: z.array(z.string()),\n source: z.string().default('exit-ticket'),\n version: z.string().default('1.1'),\n createdDate: z.string().optional(),\n lastModified: z.string().optional(),\n})\n\nexport type QuestionMetadata = z.infer<typeof QuestionMetadataSchema>\n\n// ============================================================================\n// Question Schema (v1.1)\n// ============================================================================\n\n/**\n * Full question schema with v1.1 enhancements:\n * - questionArchetype: Pedagogical classification\n * - misconceptionTargets: Expected misconceptions this question probes\n * - options with misconceptionId and structured feedback\n */\nexport const QuestionSchema = z.object({\n // Identifiers\n questionId: z.string().regex(/^[a-z0-9-]+-l\\d+-q\\d+$/),\n globalId: z.string().regex(/^exit-ticket-\\d{4}$/),\n questionNumber: z.number().int().positive().max(5),\n\n // Classification\n questionType: QuestionTypeSchema,\n questionTypeLabel: z.string(),\n questionArchetype: QuestionArchetypeSchema.optional(), // v1.1\n\n // Content\n prompt: z.string().min(10),\n hasCodeBlock: z.boolean(),\n codeLanguage: z.string().nullable(),\n codeContent: z.string().nullable(),\n\n // Misconception targeting (v1.1)\n misconceptionTargets: z.array(z.string()).optional(),\n\n // Answer options (exactly 4)\n options: z.array(OptionSchema).length(4),\n\n // Correct answer\n correctAnswer: OptionKeySchema,\n correctAnswerText: z.string(),\n\n // Metadata\n metadata: QuestionMetadataSchema,\n})\n\nexport type Question = z.infer<typeof QuestionSchema>\n\n// ============================================================================\n// Question without answer (for Standard tier - no answer leakage)\n// ============================================================================\n\nexport const QuestionWithoutAnswerSchema = QuestionSchema.omit({\n correctAnswer: true,\n correctAnswerText: true,\n}).extend({\n options: z.array(\n OptionSchema.omit({\n isCorrect: true,\n feedback: true,\n misconceptionId: true,\n })\n ).length(4),\n})\n\nexport type QuestionWithoutAnswer = z.infer<typeof QuestionWithoutAnswerSchema>\n","import { z } from 'zod'\nimport { QuestionSchema } from './question'\n\n// ============================================================================\n// Course Domain & Tier\n// ============================================================================\n\nexport const CourseDomainSchema = z.enum([\n // AI & Data Science\n 'ai_data_science',\n 'ai_ml_cv',\n 'ai_generative',\n 'ai_advanced',\n // Web Development\n 'web_development',\n // Mobile Development\n 'mobile_development',\n 'mobile',\n // Block-based Programming\n 'block_based',\n 'python_programming',\n 'design',\n // Foundation\n 'foundation',\n 'creative_computing',\n])\n\nexport type CourseDomain = z.infer<typeof CourseDomainSchema>\n\nexport const CourseTierSchema = z.enum([\n 'foundation',\n 'intermediate',\n 'advanced',\n])\n\nexport type CourseTier = z.infer<typeof CourseTierSchema>\n\n// ============================================================================\n// Lesson Schema\n// ============================================================================\n\nexport const LessonSchema = z.object({\n lessonId: z.string(), // e.g., \"ai-1-lesson-1\"\n lessonNumber: z.number().int().positive(),\n lessonTitle: z.string(),\n lessonSlug: z.string().optional(),\n totalQuestions: z.number().int().positive().default(5),\n questions: z.array(QuestionSchema),\n})\n\nexport type Lesson = z.infer<typeof LessonSchema>\n\n// ============================================================================\n// Course Schema\n// ============================================================================\n\nexport const CourseSchema = z.object({\n courseId: z.string(), // e.g., \"ai-1\"\n courseName: z.string(), // e.g., \"AI-1 Data Analysis and Data Science\"\n courseCode: z.string(), // e.g., \"AI1\"\n domain: CourseDomainSchema,\n tier: CourseTierSchema,\n difficulty: z.number().int().min(1).max(5),\n totalLessons: z.number().int().positive(),\n totalQuestions: z.number().int().positive(),\n sourceFile: z.string().optional(),\n lessons: z.array(LessonSchema),\n})\n\nexport type Course = z.infer<typeof CourseSchema>\n\n// ============================================================================\n// Course Summary (without questions - for listing)\n// ============================================================================\n\nexport const CourseSummarySchema = CourseSchema.omit({ lessons: true }).extend({\n lessons: z.array(\n LessonSchema.omit({ questions: true })\n ).optional(),\n})\n\nexport type CourseSummary = z.infer<typeof CourseSummarySchema>\n","import { z } from 'zod'\nimport { OptionKeySchema, FeedbackSchema } from './question'\n\n// ============================================================================\n// User Response Schema\n// ============================================================================\n\n/**\n * A single user response to a question\n */\nexport const UserResponseSchema = z.object({\n questionId: z.string(),\n selectedAnswer: OptionKeySchema,\n timeSpent: z.number().int().nonnegative().optional(), // milliseconds\n submittedAt: z.string().datetime().optional(),\n})\n\nexport type UserResponse = z.infer<typeof UserResponseSchema>\n\n// ============================================================================\n// Assessment Submission Schema\n// ============================================================================\n\n/**\n * An assessment submission containing multiple responses\n */\nexport const AssessmentSubmissionSchema = z.object({\n assessmentId: z.string().uuid().optional(), // Generated if not provided\n userId: z.string().optional(), // For authenticated users\n sessionId: z.string().optional(), // For anonymous sessions\n courseId: z.string(),\n lessonId: z.string().optional(),\n responses: z.array(UserResponseSchema).min(1),\n submittedAt: z.string().datetime(),\n metadata: z.record(z.unknown()).optional(), // Custom metadata\n})\n\nexport type AssessmentSubmission = z.infer<typeof AssessmentSubmissionSchema>\n\n// ============================================================================\n// Graded Response Schema (Premium tier)\n// ============================================================================\n\n/**\n * Result of grading a single response\n */\nexport const GradedResponseSchema = z.object({\n questionId: z.string(),\n selectedAnswer: OptionKeySchema,\n correctAnswer: OptionKeySchema,\n isCorrect: z.boolean(),\n misconceptionId: z.string().nullable(), // For incorrect answers\n feedback: FeedbackSchema.optional(),\n timeSpent: z.number().int().nonnegative().optional(),\n})\n\nexport type GradedResponse = z.infer<typeof GradedResponseSchema>\n\n// ============================================================================\n// Misconception Report Schema (Premium tier)\n// ============================================================================\n\n/**\n * Aggregated misconception analysis for an assessment\n */\nexport const MisconceptionReportSchema = z.object({\n misconceptionId: z.string(),\n count: z.number().int().positive(),\n percentage: z.number().min(0).max(100),\n questionIds: z.array(z.string()),\n description: z.string().optional(),\n})\n\nexport type MisconceptionReport = z.infer<typeof MisconceptionReportSchema>\n\n// ============================================================================\n// Graded Assessment Schema (Premium tier)\n// ============================================================================\n\n/**\n * Complete graded assessment with scores and misconception analysis\n */\nexport const GradedAssessmentSchema = z.object({\n assessmentId: z.string().uuid(),\n userId: z.string().optional(),\n courseId: z.string(),\n lessonId: z.string().optional(),\n\n // Scores\n totalQuestions: z.number().int().positive(),\n correctCount: z.number().int().nonnegative(),\n incorrectCount: z.number().int().nonnegative(),\n score: z.number().min(0).max(100), // Percentage\n passed: z.boolean(), // Based on threshold (default 70%)\n\n // Detailed results\n responses: z.array(GradedResponseSchema),\n\n // Misconception analysis (v1.1 feature)\n misconceptions: z.array(MisconceptionReportSchema).optional(),\n topMisconceptions: z.array(z.string()).max(3).optional(),\n\n // Timing\n totalTimeSpent: z.number().int().nonnegative().optional(), // milliseconds\n averageTimePerQuestion: z.number().nonnegative().optional(),\n submittedAt: z.string().datetime(),\n gradedAt: z.string().datetime(),\n})\n\nexport type GradedAssessment = z.infer<typeof GradedAssessmentSchema>\n\n// ============================================================================\n// Validation Result Schema (Standard tier - no answer leakage)\n// ============================================================================\n\n/**\n * Result of validating an assessment submission\n * Does NOT reveal correct answers - only schema compliance\n */\nexport const ValidationResultSchema = z.object({\n valid: z.boolean(),\n errors: z.array(z.object({\n path: z.array(z.union([z.string(), z.number()])),\n message: z.string(),\n code: z.string().optional(),\n })).optional(),\n isComplete: z.boolean(), // All questions answered\n questionCount: z.number().int(),\n answeredCount: z.number().int(),\n})\n\nexport type ValidationResult = z.infer<typeof ValidationResultSchema>\n","import { AssessmentSubmissionSchema, type AssessmentSubmission, type ValidationResult } from '../schemas'\nimport type { ValidationClientConfig, ValidationClientResult, ValidationApiError } from './types'\n\n/**\n * Standard Tier - Validation Client\n *\n * Validates assessment submissions against schemas\n * Does NOT reveal correct answers - only schema compliance\n */\nexport class ValidationClient {\n private apiKey: string\n private baseUrl: string\n private timeout: number\n\n constructor(config: ValidationClientConfig) {\n this.apiKey = config.apiKey\n this.baseUrl = config.baseUrl ?? 'https://api.telebort.com/exit-tickets/v1'\n this.timeout = config.timeout ?? 30000\n }\n\n /**\n * Validate an assessment submission\n *\n * @param assessment - The assessment to validate\n * @returns Validation result (no correct answers revealed)\n */\n async validateAssessment(assessment: AssessmentSubmission): Promise<ValidationClientResult> {\n // First, validate locally with Zod schema\n const localResult = AssessmentSubmissionSchema.safeParse(assessment)\n\n if (!localResult.success) {\n return {\n valid: false,\n errors: localResult.error.issues.map(issue => ({\n path: issue.path,\n message: issue.message,\n code: issue.code,\n })),\n isComplete: false,\n questionCount: 0,\n answeredCount: assessment.responses?.length ?? 0,\n }\n }\n\n // If API key is provided, also validate with server\n if (this.apiKey && this.apiKey !== 'offline') {\n try {\n const response = await fetch(`${this.baseUrl}/validate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': this.apiKey,\n },\n body: JSON.stringify(assessment),\n signal: AbortSignal.timeout(this.timeout),\n })\n\n if (!response.ok) {\n const error = await response.json() as ValidationApiError\n throw new Error(`API Error: ${error.message}`)\n }\n\n return await response.json() as ValidationClientResult\n } catch (error) {\n // Fall back to local validation if API fails\n console.warn('API validation failed, using local validation:', error)\n }\n }\n\n // Return local validation result\n return {\n valid: true,\n isComplete: true, // Assume complete for local validation\n questionCount: assessment.responses.length,\n answeredCount: assessment.responses.length,\n }\n }\n\n /**\n * Validate assessment offline (no API call)\n * Uses only Zod schemas\n */\n validateOffline(assessment: unknown): ValidationResult {\n const result = AssessmentSubmissionSchema.safeParse(assessment)\n\n if (!result.success) {\n return {\n valid: false,\n errors: result.error.issues.map(issue => ({\n path: issue.path,\n message: issue.message,\n code: issue.code,\n })),\n isComplete: false,\n questionCount: 0,\n answeredCount: 0,\n }\n }\n\n return {\n valid: true,\n isComplete: true,\n questionCount: result.data.responses.length,\n answeredCount: result.data.responses.length,\n }\n }\n}\n"]}