feat: add learning objective targets
Pipeline / check (pull_request) Failing after 3m25s
Pipeline / docs (pull_request) Skipped
Pipeline / nightly (pull_request) Skipped
Pipeline / release (pull_request) Skipped

This commit is contained in:
2026-09-21 14:02:34 -04:00
parent 9755417899
commit 8ec48fb185
32 changed files with 2396 additions and 448 deletions
+18 -12
View File
@@ -2,7 +2,7 @@
// Copyright Scientific Computing Studio
// Source: https://git.scient.ing/education/coursebank
//! Rendering lecture pages, and checking what backs each objective.
//! Rendering lecture pages, and checking what backs each learning target.
//!
//! Both handlers here read the course file and nothing else, so neither needs a
//! bank or a single response. That is deliberate: a reading list is useful in week
@@ -10,7 +10,7 @@
use coursebank::course::CourseFile;
use coursebank::error::Result;
use coursebank::lecture::{objectives_markdown, readings_markdown};
use coursebank::lecture::{objectives_markdown, readings_markdown, targets_markdown};
use coursebank::yaml;
use crate::cli::{Cli, LectureCommand};
@@ -29,6 +29,10 @@ pub(crate) fn lecture(cli: &Cli, sub: &LectureCommand) -> Result<Outcome> {
objectives_markdown(&course, id, style.as_style())?,
out.as_deref(),
),
LectureCommand::Targets { id, style, out } => emit(
targets_markdown(&course, id, style.as_style())?,
out.as_deref(),
),
LectureCommand::Coverage { lecture: only } => coverage(&course, only.as_deref(), cli.quiet),
}
}
@@ -45,19 +49,21 @@ fn emit(markdown: String, out: Option<&std::path::Path>) -> Result<Outcome> {
Ok(Outcome::Ok)
}
/// Prints the readings behind each objective.
/// Prints the readings behind each learning target.
///
/// Returns [`Outcome::Findings`] when an assessed objective has no reading, since
/// that is the case where a student report can name what was missed but not where
/// to go and read about it.
/// Targets rather than objectives, because that is the tier a reading is cited
/// against: a section of a book backs a specific performance. Returns
/// [`Outcome::Findings`] when an assessed target has no reading, since that is
/// the case where a student report can name what was missed but not where to go
/// and read about it.
fn coverage(course: &CourseFile, lecture: Option<&str>, quiet: bool) -> Result<Outcome> {
let ids: Vec<String> = match lecture {
Some(l) => course
.lecture_objectives(l)
.lecture_targets(l)
.into_iter()
.map(str::to_string)
.collect(),
None => course.objectives_in_order(),
None => course.targets_in_order(),
};
for id in &ids {
@@ -83,21 +89,21 @@ fn coverage(course: &CourseFile, lecture: Option<&str>, quiet: bool) -> Result<O
if let Some(focus) = &reading.focus {
println!(
" {}",
course.expand_objective_refs(focus, |o| { course.objective_text(o) })
course.expand_objective_refs(focus, |id| { course.text_for(id) })
);
}
}
}
let gaps = course.objectives_without_readings();
let gaps = course.targets_without_readings();
if gaps.is_empty() {
if !quiet {
println!("\nevery assessed objective has a reading behind it");
println!("\nevery assessed target has a reading behind it");
}
return Ok(Outcome::Ok);
}
println!(
"\n{} assessed objective(s) with no reading, so a student report cannot say \
"\n{} assessed target(s) with no reading, so a student report cannot say \
where to go back to:",
gaps.len()
);