@@ -0,0 +1,188 @@
|
||||
# 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(())
|
||||
# }
|
||||
```
|
||||
Reference in New Issue
Block a user