Files
alexm abc0bdf621
Sync README to GitHub / sync (push) Successful in 12s
CI / check (push) Successful in 8m31s
Deploy docs / deploy (push) Successful in 6m44s
Nightly / nightly (push) Successful in 10m33s
Dev (#1)
Reviewed-on: #1
2026-08-07 15:48:11 -04:00

189 lines
6.5 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.
Add `--with-examples` if you want a filled-in bank to read rather than an empty directory to stare at.
## 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(())
# }
```