6.3 KiB
6.3 KiB
Exam Format Specification (Minimal)
Purpose
A simple, portable exam format. The system reads an exam JSON, renders it online, collects answers, and returns a JSON that bundles the original exam plus the answers (and optionally a basic result summary).
Top-level Exam Structure
- examId: string (unique)
- subject: string
- title: string
- difficulty: one of
beginner | intermediate | advanced - durationMinutes: integer ≥ 1
- sections: array of sections, each containing questions
- metadata: optional object (version, createdAt, etc.)
Question Types (required support)
- Single Choice (one correct answer)
- Multiple Choices (multiple correct answers)
- True/False
- Essay
- Simple Coding
- Coding Exercise
"I Don't Know" Option
For single_choice, multiple_choices, and true_false questions, an "I don't know" option is automatically available. This allows honest assessment without guessing.
Section Structure
- id: string
- title: string
- questions: array of questions (of any supported type)
Common Question Fields
- id: string
- type:
single_choice | multiple_choices | true_false | essay | code_simple | code_exercise - prompt: string (supports simple Markdown)
- points: integer ≥ 0
- allowIDK: boolean (optional, default true for single_choice, multiple_choices, true_false)
Type-specific Fields
-
single_choice (one correct answer)
choices: array of{ key: string, text: string }answer: string (the correctkey)allowIDK: boolean (default true) - adds "I don't know" option
-
multiple_choices (multiple correct answers)
choices: array of{ key: string, text: string }answer: array of strings (all correctkeys, e.g., ["A", "C"])allowIDK: boolean (default true) - adds "I don't know" optionpartialCredit: boolean (default true) - award partial points for some correct
-
true_false
answer: booleanallowIDK: boolean (default true) - adds "I don't know" option (scores as wrong)
-
essay
rubric:{ criteria: [{ name: string, weight: number }], maxPoints: integer }- Notes:
answeromitted; scored manually or later
-
code_simple
language:python | typescript | javascripttests: array of{ input: string, expected: string, visibility?: public|hidden }
-
code_exercise
language:python | typescript | javascripttests: array of{ input: string, expected: string, visibility?: public|hidden, weight?: number }rubric:{ criteria: [{ name: string, weight: number }], maxPoints: integer }constraints?: string (optional)starterCode?: string (optional)
Minimal Valid Exam JSON (example)
{
"examId": "sample-exam-v1",
"subject": "python",
"title": "Sample Exam",
"difficulty": "beginner",
"durationMinutes": 60,
"sections": [
{
"id": "sec-1",
"title": "Single Choice",
"questions": [
{
"id": "q1",
"type": "single_choice",
"prompt": "Which is a valid list literal?",
"choices": [
{ "key": "A", "text": "(1, 2, 3)" },
{ "key": "B", "text": "{1, 2, 3}" },
{ "key": "C", "text": "[1, 2, 3]" }
],
"answer": "C",
"points": 2
}
]
},
{
"id": "sec-2",
"title": "True / False",
"questions": [
{ "id": "q2", "type": "true_false", "prompt": "Tuples are immutable.", "answer": true, "points": 2 }
]
},
{
"id": "sec-3",
"title": "Essay",
"questions": [
{
"id": "q3",
"type": "essay",
"prompt": "Explain decorators and a common use case.",
"rubric": { "criteria": [{ "name": "Correctness", "weight": 0.6 }, { "name": "Clarity", "weight": 0.4 }], "maxPoints": 8 },
"points": 8
}
]
},
{
"id": "sec-4",
"title": "Simple Coding",
"questions": [
{
"id": "q4",
"type": "code_simple",
"language": "python",
"prompt": "Implement squares(n) returning list of squares 0..n.",
"tests": [ { "input": "squares(3)", "expected": "[0, 1, 4, 9]", "visibility": "hidden" } ],
"points": 10
}
]
},
{
"id": "sec-5",
"title": "Coding Exercise",
"questions": [
{
"id": "q5",
"type": "code_exercise",
"language": "python",
"prompt": "Implement paginate(items, page, per_page). Return items, page, per_page, total, total_pages.",
"constraints": "O(n) acceptable; validate inputs (page>=1, per_page>=1).",
"tests": [
{ "input": "paginate([1,2,3,4,5], 2, 2)", "expected": "{items:[3,4],page:2,per_page:2,total:5,total_pages:3}", "visibility": "hidden", "weight": 2 },
{ "input": "paginate([], 1, 10)", "expected": "{items:[],page:1,per_page:10,total:0,total_pages:0}", "visibility": "hidden" }
],
"rubric": { "criteria": [{ "name": "Correctness", "weight": 0.6 }, { "name": "Structure", "weight": 0.2 }, { "name": "EdgeCases", "weight": 0.2 }], "maxPoints": 20 },
"points": 20
}
]
}
]
}
Output Shape (what the system returns)
- Echoes the input exam JSON as
exam - Captured answers as
attempt
{
"exam": { "...": "(same as input)" },
"attempt": {
"attemptId": "attempt-001",
"startedAt": "2025-10-20T10:00:00Z",
"submittedAt": "2025-10-20T10:45:00Z",
"answers": [
{ "questionId": "q1", "response": "C", "timeSec": 25 },
{ "questionId": "q2", "response": true, "timeSec": 10 },
{ "questionId": "q3", "response": "Decorators wrap functions to add behavior...", "timeSec": 180 },
{ "questionId": "q4", "response": { "code": "def squares(n): ..." }, "timeSec": 420 },
{ "questionId": "q5", "response": { "code": "def paginate(items, page, per_page): ..." }, "timeSec": 900 }
]
}
}
Validation Checklist
- Required top-level fields present
- Each section and question has a unique
id points≥ 0- Type-specific fields present according to question type
Versioning
- Use semantic versioning in
metadata.versionfor exam files - New optional fields are allowed without breaking existing behavior