256 lines
9.4 KiB
Markdown
256 lines
9.4 KiB
Markdown
# Setting up a course
|
|
|
|
A course is a directory in a git repository.
|
|
Nothing lives in a database, and the tool holds no state of its own, so a course you set up in 2026 opens in 2031 with whatever version of coursebank you have then.
|
|
|
|
## Make the directory
|
|
|
|
```console
|
|
$ coursebank init --code "BIOSC 1540" --title "Computational Biology" --term 2026s
|
|
wrote course.yaml
|
|
wrote 4 JSON Schema files
|
|
```
|
|
|
|
That gives you:
|
|
|
|
| Path | Holds | Written by |
|
|
|:--|:--|:--|
|
|
| `course.yaml` | identity, grading policy, objectives, lectures | you |
|
|
| `banks/` | items, with design intent and pooled statistics | you, then `calibrate` |
|
|
| `assessments/` | what was given, to whom, when | `assemble`, then you |
|
|
| `data/` | one row per student per item | `ingest` |
|
|
| `reports/` | generated Markdown and HTML | `report` |
|
|
| `build/` | exports: QTI packages, `.typ` files, PDFs | `export` |
|
|
| `schema/` | JSON Schemas for editor validation | `init`, `schema` |
|
|
|
|
`build/` and `reports/` are in the generated `.gitignore`.
|
|
The other four are the repository's content and belong in review.
|
|
|
|
If the directory already has a `.gitignore`, `init` keeps it and inserts only the patterns it was missing at the top, so running this inside an existing repository costs you nothing.
|
|
|
|
Add `--with-examples` if you want a filled-in bank to read rather than an empty directory to stare at.
|
|
|
|
## Declare your texts once
|
|
|
|
Every work the course cites goes in `references`, keyed by the citation key you would use in a `.bib` file.
|
|
|
|
```yaml
|
|
references:
|
|
kuriyan2013molecules:
|
|
label: KKW
|
|
kind: book
|
|
role: required
|
|
title: 'The molecules of life: Physical and chemical principles'
|
|
authors: ['Kuriyan, John', 'Konforti, Boyana', 'Wemmer, David']
|
|
year: 2013
|
|
publisher: W. W. Norton & Company
|
|
base_url: https://library.scient.ing/kuriyan2013molecules/
|
|
note: On reserve at the Bevier Engineering Library.
|
|
```
|
|
|
|
`label` is the short form a reading list shows, and it has to name one work, because reports print it instead of the key.
|
|
`base_url` is what a reading's `path` is joined to, so the key appears once in the file rather than once per reading.
|
|
|
|
## Point readings at objectives
|
|
|
|
A reading names a location inside a reference and lists the objectives it serves.
|
|
|
|
```yaml
|
|
lectures:
|
|
L1.1:
|
|
title: Enthalpy
|
|
readings:
|
|
- ref: kuriyan2013molecules
|
|
locator: '§1.3'
|
|
path: '1/A/#3'
|
|
objectives: [lo-water-attenuation, lo-coulomb-estimate]
|
|
summary: >-
|
|
Ionic interactions: favorable in vacuum, attenuated ~80-fold by water.
|
|
focus: >-
|
|
The two magnitudes and the factor of 80.
|
|
skip: >-
|
|
Skip the unit-conversion derivation.
|
|
```
|
|
|
|
The three prose fields answer three different questions, and each has a different reader.
|
|
`summary` says what the section contains, `focus` says what to take from it, and `skip` says what to ignore.
|
|
A student report quotes `focus` at somebody who missed the objective; a lecture page prints all three.
|
|
|
|
The mapping lives on the reading rather than on the objective because objectives outlive editions.
|
|
When a textbook renumbers its sections, one block of `readings` changes and `learning_objectives` does not.
|
|
Going the other way is a scan: `coursebank lecture coverage` lists the readings behind each objective and flags the ones with none.
|
|
|
|
Set `order` on each objective if you want a lecture page to number them in teaching order.
|
|
The registry is a map, so declaration order is lost on load, and sorting by id would put `lo-enthalpy` ahead of `lo-first-law`.
|
|
|
|
A reading written as a plain string, which is what this field held before, still loads and is written back out unchanged.
|
|
|
|
## Generate the reading list
|
|
|
|
```console
|
|
$ coursebank lecture readings L1.1 --out lectures/l1_1-readings.qmd
|
|
wrote lectures/l1_1-readings.qmd
|
|
```
|
|
|
|
Objective numbers in the generated page (`_(LO 4, 7)_`) are positional, so they are computed at render time rather than written down.
|
|
Insert an objective and everything after it renumbers on the next build.
|
|
|
|
## Point your editor at the schemas
|
|
|
|
The schemas are the difference between authoring items and looking up field names.
|
|
With them wired in, your editor completes `cognitive_process` from the eleven legal values and underlines a typo in `learning_objectives` as you type.
|
|
|
|
Put the modeline at the top of each file:
|
|
|
|
```yaml
|
|
# yaml-language-server: $schema=../schema/bank.schema.json
|
|
```
|
|
|
|
`coursebank schema` reprints the paths and the exact line to paste.
|
|
Rerun it after upgrading, since the schemas ship with the binary.
|
|
|
|
## Fill in `course.yaml`
|
|
|
|
Four registries live here, and everything else references them by id.
|
|
|
|
### Policy
|
|
|
|
Conventions stated once instead of per assessment:
|
|
|
|
```yaml
|
|
policy:
|
|
points_per_item: 1.5
|
|
options_per_item: 5
|
|
bonus_levels: [5]
|
|
allow_partial_credit: true
|
|
partial_credit_floor_level: 3
|
|
mastery_threshold: 0.75
|
|
min_items_for_mastery: 3
|
|
```
|
|
|
|
`partial_credit_floor_level: 3` is the one to think about.
|
|
Below Apply, a defensible wrong answer usually means the item is unclear rather than that the student partly understood something.
|
|
Setting a floor makes that a rule you decided once, so it stops being an argument you have every term with a student at your desk.
|
|
|
|
### Units and lectures
|
|
|
|
Units are the coarse grouping.
|
|
Lectures carry a date and belong to a unit:
|
|
|
|
```yaml
|
|
units:
|
|
- id: u1
|
|
title: Sequence analysis
|
|
description: Alignment, scoring models, and database search.
|
|
|
|
lectures:
|
|
l09:
|
|
title: Pairwise alignment
|
|
date: 2026-02-10
|
|
unit: u1
|
|
readings:
|
|
- "Durbin et al., ch. 2"
|
|
```
|
|
|
|
The dates are what let a student report say which lecture to review, and what lets `assemble --lectures l09,l10` draw only from material you have taught.
|
|
|
|
### Learning objectives
|
|
|
|
The load-bearing registry.
|
|
An objective's wording lives in exactly one place, so rewording it updates every report that quotes it:
|
|
|
|
```yaml
|
|
learning_objectives:
|
|
lo-align-algorithm:
|
|
text: Trace the dynamic programming recurrence for a global or local alignment and explain what each term contributes.
|
|
unit: u1
|
|
lectures: [l09]
|
|
level_ceiling: 4
|
|
tags: [algorithms]
|
|
|
|
lo-align-scoring:
|
|
text: Predict how changing a substitution matrix or gap penalty changes the resulting alignment.
|
|
unit: u1
|
|
lectures: [l10]
|
|
prerequisites: [lo-align-algorithm]
|
|
level_ceiling: 4
|
|
```
|
|
|
|
Write the text in the second person and start with a verb, because reports quote it verbatim to students.
|
|
|
|
Three fields do work later that is easy to miss now.
|
|
`prerequisites` is walked backwards by student reports to suggest where to start reviewing, so a student who missed the scoring objective gets pointed at the algorithm first.
|
|
`level_ceiling` is the highest level you intend to assess the objective at; placing an item above it is a warning, which means either the item overreaches or the ceiling needs raising, and both are useful to be asked about.
|
|
`assessed: false` marks an objective you teach but measure some other way, such as by project rubric, which stops coverage reporting from flagging it as a gap on every run.
|
|
|
|
Objective ids are join keys.
|
|
Renaming one orphans every item and every stored response that referenced it, so pick names you can live with.
|
|
|
|
### Stimuli
|
|
|
|
A shared passage, table, or figure that several items ask about:
|
|
|
|
````yaml
|
|
stimuli:
|
|
s-dock-poses:
|
|
body: |
|
|
A docking run produces five poses of the same ligand. Scores are in
|
|
kcal/mol; RMSD is measured against the crystallographic pose.
|
|
|
|
```
|
|
Pose Score RMSD (Å) Cluster size
|
|
1 -9.8 6.2 3
|
|
2 -9.4 1.1 28
|
|
```
|
|
caption: Docking output for a single ligand against one receptor.
|
|
````
|
|
|
|
Items reference it with `stimulus: s-dock-poses`.
|
|
Declaring it here rather than pasting it into four items means a correction to the table happens once.
|
|
|
|
## Check it
|
|
|
|
```console
|
|
$ coursebank validate
|
|
course.yaml: ok
|
|
banks: 0 files, 0 items
|
|
assessments: 0 records
|
|
```
|
|
|
|
`validate` enforces what must be true: every reference resolves, ids are unique, keys are present, credit is in range.
|
|
It reports everything wrong in one pass instead of one problem per run, because fixing one typo per invocation is not a workflow.
|
|
|
|
`lint` is separate and advises on what is usually a mistake: an option that gives away the answer by being longer than the others, a stem with no task in it, a level that disagrees with the cognitive process.
|
|
Every rule has a code you can silence.
|
|
The split matters because a linter that blocks a commit for a style opinion gets disabled, and then you lose the validator with it.
|
|
|
|
Exit codes are meaningful.
|
|
`0` means success, `1` means the command failed, and `2` means validation or linting found something.
|
|
|
|
## Where to go next
|
|
|
|
[`authoring`](crate::guide::authoring) writes the first bank.
|
|
[`first_exam`](crate::guide::first_exam) takes an exam from blueprint to student report.
|
|
|
|
## Reading a course from Rust
|
|
|
|
The CLI is one caller.
|
|
[`Catalog::load`](crate::catalog::Catalog::load) reads a whole course directory and indexes every item by global id:
|
|
|
|
```rust,no_run
|
|
use std::path::Path;
|
|
|
|
use coursebank::Catalog;
|
|
|
|
# fn main() -> coursebank::Result<()> {
|
|
let catalog = Catalog::load(Path::new("path/to/course"))?;
|
|
|
|
println!("{} items across {} banks", catalog.entries.len(), catalog.banks.len());
|
|
|
|
// Global ids are `bank::item`. Bare ids resolve when unambiguous.
|
|
let entry = catalog.require("sequence-analysis::q-align-recall-001")?;
|
|
println!("{}", entry.item.display_title());
|
|
# Ok(())
|
|
# }
|
|
```
|