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
+24 -26
View File
@@ -509,12 +509,9 @@ pub fn student(
.objectives
.iter()
.map(|mastery| ObjectiveRow {
id: mastery.objective.clone(),
id: mastery.id.clone(),
text: mastery.text.clone(),
unit: course
.learning_objectives
.get(&mastery.objective)
.and_then(|o| o.unit.clone()),
unit: course.objective_unit(&mastery.id).map(str::to_string),
n_items: mastery.n_items,
credit: mastery.credit,
rate: mastery.rate,
@@ -700,11 +697,11 @@ fn question_row(
number: row.item_number,
position: row.form_position.filter(|p| *p != row.item_number),
level: row.level.map(|l| l.code()),
objectives: row.learning_objectives.clone(),
objectives: row.learning_targets.clone(),
objective_texts: if missed {
row.learning_objectives
row.learning_targets
.iter()
.map(|id| catalog.course.objective_text(id))
.map(|id| catalog.course.text_for(id))
.collect()
} else {
// Only where it earns its space. Every question already carries its
@@ -830,15 +827,13 @@ fn lecture_focus(catalog: &Catalog, rows: &[&Response], opts: &Options) -> Vec<L
.extend(source.slides.iter().copied());
}
}
for objective in &row.learning_objectives {
if let Some(entry) = course.learning_objectives.get(objective) {
lectures.extend(entry.lectures.iter().cloned());
}
for target in &row.learning_targets {
lectures.extend(course.lectures_for(target).iter().cloned());
}
for lecture in lectures {
let tally = tallies.entry(lecture.clone()).or_default();
tally.objectives.extend(row.learning_objectives.clone());
tally.objectives.extend(row.learning_targets.clone());
tally.questions.insert(row.item_number);
if let Some(numbers) = slides.get(&lecture) {
tally.slides.extend(numbers.iter().copied());
@@ -866,7 +861,7 @@ fn lecture_focus(catalog: &Catalog, rows: &[&Response], opts: &Options) -> Vec<L
objectives: tally
.objectives
.iter()
.map(|id| course.objective_text(id))
.map(|id| course.text_for(id))
.collect(),
lecture,
}
@@ -1390,17 +1385,22 @@ pub fn cohort(
.collect();
// Objective counts come from the responses so that an objective assessed by
// two items is not reported as if it had one.
// two items is not reported as if it had one. Counted per objective, and by
// item number, so a question tagged with two of that objective's targets is
// one item here.
let mut objective_items: BTreeMap<String, BTreeSet<u32>> = BTreeMap::new();
for row in set.rows.iter().filter(|r| r.counts()) {
for objective in &row.learning_objectives {
for objective in &row.learning_targets {
objective_items
.entry(objective.clone())
.entry(course.objective_for(objective).to_string())
.or_default()
.insert(row.item_number);
}
}
// Objectives, not targets: this table is the reteaching queue, and it is
// only usable if it is short enough to read and each row rests on enough
// items to believe.
let mut objectives: Vec<CohortObjectiveRow> = cohort
.objective_rates
.iter()
@@ -1410,7 +1410,7 @@ pub fn cohort(
let mut not_yet = 0;
let mut thin = 0;
for student in &cohort.students {
if let Some(row) = student.objectives.iter().find(|o| &o.objective == id) {
if let Some(row) = student.objectives.iter().find(|o| &o.id == id) {
match row.status {
Mastery::Meeting => meeting += 1,
Mastery::Developing => developing += 1,
@@ -1421,7 +1421,7 @@ pub fn cohort(
}
CohortObjectiveRow {
id: id.clone(),
text: course.objective_text(id),
text: course.text_for(id),
n_items: objective_items.get(id).map(|s| s.len()).unwrap_or(0),
rate: *rate,
meeting,
@@ -1451,7 +1451,7 @@ pub fn cohort(
.map(|p| {
(
p.number,
(p.level.map(|l| l.code()), p.learning_objectives.clone()),
(p.level.map(|l| l.code()), p.learning_targets.clone()),
)
})
.collect();
@@ -1467,7 +1467,7 @@ pub fn cohort(
level: meta.and_then(|m| m.0),
objectives: meta.map(|m| m.1.clone()).unwrap_or_default(),
objective_texts: meta
.map(|m| m.1.iter().map(|id| course.objective_text(id)).collect())
.map(|m| m.1.iter().map(|id| course.text_for(id)).collect())
.unwrap_or_default(),
taught_in: item
.item_ref
@@ -1564,7 +1564,7 @@ pub fn cohort(
questions,
revise,
forms: form_rows(set, cohort),
blueprint: crate::select::check_blueprint(record),
blueprint: crate::select::check_blueprint(record, course),
patterns: cohort
.archetypes
.iter()
@@ -1739,10 +1739,8 @@ fn lecture_rows(
lectures.insert(source.lecture.clone());
}
}
for objective in &question.objectives {
if let Some(entry) = course.learning_objectives.get(objective) {
lectures.extend(entry.lectures.iter().cloned());
}
for target in &question.objectives {
lectures.extend(course.lectures_for(target).iter().cloned());
}
for lecture in lectures {
items.entry(lecture.clone()).or_default().push(question);