189 lines
6.5 KiB
Markdown
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(())
|
|
# }
|
|
```
|