# 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 correct `key`) - `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 correct `key`s, e.g., ["A", "C"]) - `allowIDK`: boolean (default true) - adds "I don't know" option - `partialCredit`: boolean (default true) - award partial points for some correct - **true_false** - `answer`: boolean - `allowIDK`: boolean (default true) - adds "I don't know" option (scores as wrong) - **essay** - `rubric`: `{ criteria: [{ name: string, weight: number }], maxPoints: integer }` - Notes: `answer` omitted; scored manually or later - **code_simple** - `language`: `python | typescript | javascript` - `tests`: array of `{ input: string, expected: string, visibility?: public|hidden }` - **code_exercise** - `language`: `python | typescript | javascript` - `tests`: 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) ```json { "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` ```json { "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.version` for exam files - New optional fields are allowed without breaking existing behavior