Port Igniter
Get Started

Quiz & Bank Files

The quiz file format with its grading, attempts, timing, delivery, and feedback rules, question selection, and the exact shape of all ten question types.

LMS Support Home

A quiz file holds rules and a selection of questions. The questions themselves live in bank files and are pulled in by reference.

Quiz files

Each quiz lives in quizzes/<quiz-id>.quiz.json.

{
  "schema_version": 1,
  "id": "final-test",
  "type": "quiz",
  "mode": "test",
  "instructions_md": "Shown before the learner starts.",
  "grading":  { "pass_mark_percent": 70, "score_policy": "best", "essay_gate": "wait_for_review" },
  "attempts": { "max": 3, "cooldown_minutes": 5 },
  "timing":   { "limit_minutes": 20, "grace_seconds": 10, "sync_interval_seconds": 15, "on_expire": "auto_submit" },
  "delivery": { "shuffle_questions": true, "shuffle_options": true, "one_per_page": true, "allow_back": true },
  "feedback": { "timing": "after_submit", "show_correct_answers": true, "show_rationale": true },
  "selection": [
    { "kind": "fixed",  "bank": "final-pool", "title": "Core", "questions": ["p1", "p2"] },
    { "kind": "random", "bank": "final-pool", "title": "Random", "count": 4, "filter": { "difficulty": ["easy", "medium"] } }
  ]
}

schema_version, id, type ("quiz"), and selection are required. Everything else is optional and takes the defaults below.

Group Key Values (default) Meaning
mode quiz, test A label only.
instructions_md Markdown Shown before starting.
grading pass_mark_percent 0 to 100, or null (null) With null, any submission completes the quiz.
score_policy best, latest, average (best) Which attempts count for completion and unlock rules.
essay_gate wait_for_review, auto_only (wait_for_review) Hold the result until essays are graded, or judge on auto-graded questions only.
attempts max 1 to 100, or null (unlimited)
cooldown_minutes 0 to 525,600 (0) Wait between attempts.
timing limit_minutes 1 to 1,440, or null (untimed) Enforced by the server clock.
grace_seconds 0 to 120 (10) Allowance for network lag.
sync_interval_seconds 5 to 120 (15) How often answers are saved and the clock re-synced.
on_expire auto_submit Saved answers are submitted when time runs out.
delivery shuffle_questions Boolean (false)
shuffle_options Boolean (false)
one_per_page Boolean (false)
allow_back Boolean (true) Only used with one_per_page.
feedback timing immediate, after_submit, after_final_attempt, never (after_submit) With immediate, learners check each answer as they go, and a checked answer locks.
show_correct_answers Boolean (true)
show_rationale Boolean (true) Shows each question’s rationale_md.

Selection

selection is a list of 1 to 50 pulls, asked in order:

  • Fixed: { "kind": "fixed", "bank": "<bank-id>", "questions": ["q1", "q2"] } asks exactly those questions (1 to 200). Every ID must exist in the bank.
  • Random: { "kind": "random", "bank": "<bank-id>", "count": 5, "filter": { ... } } draws count questions (1 to 200) on each attempt. The optional filter can require tags (all listed), difficulty (any listed), types (any listed), and objectives (all listed).

Both kinds accept an optional title, up to 80 characters, shown as a section heading. A question is never asked twice in one attempt, and a random pull must be satisfiable: its count can’t exceed the matching questions left after its filter and earlier pulls.

Bank files

Each bank lives in banks/<bank-id>.bank.json.

{ "schema_version": 1, "id": "git-basics", "title": "Git basics", "description": "Optional.", "questions": [ ] }

schema_version, id, and questions (up to 1,000) are required. title is up to 120 characters, and description up to 500.

Fields on every question

Field Required Rules
id Yes A local ID, unique in the bank.
type Yes One of the ten types below.
stem_md Yes The question, in Markdown, up to 5,000 characters. Never put the answer in it.
points No Greater than 0, up to 1,000. Default 1.
difficulty No easy, medium, or hard.
bloom No remember, understand, apply, analyze, evaluate, or create.
tags No Up to 20 strings.
objectives No Up to 20 objective IDs from course.json.
rationale_md No The explanation shown after answering. Always write one.
hint_md No Up to 1,000 characters.
media No { "image": "assets/...", "alt": "..." }.
source_ref No Where the fact comes from, up to 200 characters.

The ten question types

single_choice

2 to 10 options, with exactly one "correct": true. Each option can have feedback_md.

{ "id": "sc1", "type": "single_choice", "stem_md": "Which command saves a snapshot?",
  "options": [
    { "id": "a", "text_md": "`git commit`", "correct": true },
    { "id": "b", "text_md": "`git init`", "correct": false, "feedback_md": "That creates a repository." } ],
  "rationale_md": "Commit records the staged snapshot." }

multi_select

2 to 10 options, at least one correct. scoring is all_or_nothing (default) or partial, which gives credit per correct pick, minus per wrong pick, never below zero.

{ "id": "ms1", "type": "multi_select", "stem_md": "Which are branch commands? Select all that apply.", "scoring": "partial",
  "options": [ { "id": "a", "text_md": "`git switch -c x`", "correct": true }, { "id": "b", "text_md": "`git init`", "correct": false },
               { "id": "c", "text_md": "`git merge x`", "correct": true } ] }

true_false

{ "id": "tf1", "type": "true_false", "stem_md": "Git stores snapshots.", "answer": true }

short_answer

accepted is 1 to 30 strings. match is normalized (default: trims and collapses spaces, but is case-sensitive), case_insensitive, exact, or regex (a PCRE pattern without delimiters, such as ^git\s+init$, with (?i) for case-insensitive).

{ "id": "sa1", "type": "short_answer", "stem_md": "Which command creates a repository?", "match": "case_insensitive", "accepted": ["git init", "init"] }

numeric

answer is a number. Optional: tolerance of { "mode": "absolute" | "percent", "value": 0 }, and a unit up to 20 characters.

{ "id": "nu1", "type": "numeric", "stem_md": "3 commits plus 2 more is how many?", "answer": 5, "tolerance": { "mode": "absolute", "value": 0 }, "unit": "commits" }

essay

Graded by hand. Optional: min_words, max_words, and grader_notes_md, which only reviewers see.

{ "id": "es1", "type": "essay", "stem_md": "Explain why teams use branches.", "points": 5, "min_words": 30, "max_words": 300, "grader_notes_md": "Look for: parallel work, isolation, safe merging." }

cloze

Fill in the blanks. text_md contains one {{blank-id}} marker per blank, and blanks defines 1 to 20 blanks. A select blank has 2 to 10 options with exactly one correct. A text blank has accepted answers and an optional match, as in short answer. scoring is all_or_nothing (default) or per_blank.

{ "id": "cl1", "type": "cloze", "stem_md": "Complete the sentence.", "text_md": "Run {{b1}} to stage and {{b2}} to save.", "scoring": "per_blank",
  "blanks": [
    { "id": "b1", "kind": "select", "options": [ { "id": "a", "text": "git add", "correct": true }, { "id": "b", "text": "git push", "correct": false } ] },
    { "id": "b2", "kind": "text", "match": "case_insensitive", "accepted": ["git commit", "commit"] } ] }

ordering

items (2 to 12) listed in the correct order. The server shuffles them. scoring is all_or_nothing (default) or partial.

{ "id": "or1", "type": "ordering", "stem_md": "Put the steps in order.", "scoring": "partial",
  "items": [ { "id": "s1", "text_md": "Edit a file" }, { "id": "s2", "text_md": "`git add`" }, { "id": "s3", "text_md": "`git commit`" } ] }

matching

left (2 to 10), right (2 to 12, extras act as distractors), and pairs. Every left item needs exactly one pair, and no two left items can share a right item. scoring is all_or_nothing (default) or per_pair.

{ "id": "ma1", "type": "matching", "stem_md": "Match command to effect.", "scoring": "per_pair",
  "left":  [ { "id": "l1", "text_md": "`git init`" }, { "id": "l2", "text_md": "`git merge x`" } ],
  "right": [ { "id": "r1", "text_md": "Starts a repository" }, { "id": "r2", "text_md": "Combines a branch" }, { "id": "r3", "text_md": "Deletes history" } ],
  "pairs": [ { "left": "l1", "right": "r1" }, { "left": "l2", "right": "r2" } ] }

categorize

categories (2 to 8) and items (2 to 20), where each item names its category. scoring is all_or_nothing (default) or per_item.

{ "id": "ca1", "type": "categorize", "stem_md": "Sort the commands.", "scoring": "per_item",
  "categories": [ { "id": "save", "title": "Saving work" }, { "id": "branch", "title": "Branching" } ],
  "items": [ { "id": "i1", "text_md": "`git commit`", "category": "save" }, { "id": "i2", "text_md": "`git merge`", "category": "branch" } ] }

Writing good questions

  • Test one clear skill per question, and tie it to an objective.
  • Make distractors plausible and about as long as the right answer. Avoid “all of the above.”
  • Never make the right answer always the first or the longest option.
  • Give every question an explanation that says why, and per-option feedback for tempting wrong answers.
  • List every accepted spelling for short answers.
  • For random pools, write at least twice as many questions as one attempt draws.
Top