first working version
This commit is contained in:
183
docs/exam-format.md
Normal file
183
docs/exam-format.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user