Files
lnet_tutor/docs/exam-format.md
2025-10-22 20:14:31 +08:00

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 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 keys, 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)

{
  "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.version for exam files
  • New optional fields are allowed without breaking existing behavior