Authoring and validating question banks
Source:vignettes/authoring-question-banks.Rmd
authoring-question-banks.RmdThe index schema
The index is a UTF-8 CSV with one row per question and seven required columns:
| Column | Meaning |
|---|---|
id |
Stable, unique identifier used for selection and variable prefixes |
file |
Absolute path or, preferably, a path relative to the index |
title |
Human-readable catalogue title |
topic |
Filterable topic label |
difficulty |
Filterable difficulty label such as easy or
hard
|
marks |
Positive numerical mark value |
type |
One of numerical, shortanswer,
singlechoice, or multichoice
|
Additional columns are allowed and are carried into
selected-questions.csv. This makes it possible to add
features such as week, learning outcome, source dataset, author, or
review status without changing the package.
Relative paths make a bank portable. For example:
id,file,title,topic,difficulty,marks,type
joins-01,questions/joins/joins-01.qmd,Choose a join,Joins,easy,1,singlechoice
The fragment contract
Each .qmd or .Rmd fragment must:
- contain no YAML front matter;
- have exactly one H2 (
##) question heading; - have exactly one complete
::: unilur-solutionblock; - use R chunk labels that are unique across the entire bank;
- prefix question variables with
q_<question_id>_, replacing hyphens in the ID with underscores; and - call the helper corresponding to the indexed
type.
A numerical fragment looks like this:
## Calculate a total
What is six multiplied by seven?
**Answer:** `r quiz_numeric(q_arithmetic_01_answer)`
::: unilur-solution
Multiplication gives **`r q_arithmetic_01_answer`**.
:::Do not put YAML in fragments. build_quiz() creates
complete wrapper documents and supplies their YAML metadata.
Answer helpers
Use exactly one of these families for the indexed type:
quiz_numeric(answer = 42, tolerance = 0)
quiz_shortanswer(answer = "left_join")
quiz_singlechoice(
answer = "left_join",
options = c("inner_join", "left_join", "anti_join")
)
quiz_multichoice(
answer = c("filter", "select"),
options = c("filter", "select", "lm", "hist")
)The helpers adapt to the output target. Student HTML receives empty
input controls, teacher HTML marks correct responses, PDFs receive
printable fields or symbols, and Moodle receives cloze grading syntax.
Do not call moodlequiz::cloze() directly in a fragment,
because doing so would make the student and teacher versions
inconsistent.
For single-choice questions, answer should equal exactly
one entry in options. For multiple-choice questions, every
answer should appear in options. Keep options as character
vectors so that labels render predictably.
Add a question step by step
- Choose a stable ID. Avoid renaming it after quizzes have used it.
- Add a
.qmdfragment belowquestions/. - Give every R chunk a bank-wide unique label.
- Prefix temporary variables using the ID rule.
- Use the matching answer helper.
- Add a worked solution block.
- Add the index row, including its relative file path and metadata.
- Run
validate_question_bank(index_file). - Run a quick source build with
render = FALSE. - Render and review both student and teacher versions before publishing.
What validation catches
validate_question_bank() checks the full catalogue for
missing or blank metadata, duplicate IDs, invalid marks, unsupported
types, missing files, front matter in fragments, missing or repeated H2
headings and solution blocks, duplicate chunk labels, incorrect variable
prefixes, direct cloze calls, and a mismatch between the indexed type
and answer helper.
Validation is structural. It cannot decide whether a question is pedagogically clear or whether its answer is factually correct. Peer review and rendered teacher-version review remain essential.
Version-control recommendations
Commit the index and fragments. Usually ignore generated wrappers and rendered outputs, or publish reviewed outputs as release artifacts. Record the version of any data package or external source used to calculate answers. Review index changes as carefully as code because changing marks, type, or file mappings can change an assessment.