feat: improve dropped question support

This commit is contained in:
2026-09-20 00:41:26 -04:00
parent ba84c4d82a
commit cea03048b8
18 changed files with 302 additions and 86 deletions
+33 -13
View File
@@ -172,12 +172,14 @@ pub struct StudentDiagnostic {
/// One row per question, with no question in it.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub questions: Vec<QuestionRow>,
/// Questions dropped from scoring, in number order.
/// Questions thrown out, in number order.
///
/// Reported separately from the question rows so the document can say once,
/// in prose, why the denominator is smaller than the paper was.
/// in prose, what happened to them. The two kinds need different sentences:
/// a removed question is gone from the denominator, and a full-credit
/// question is still in it.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub dropped_questions: Vec<u32>,
pub dropped_questions: Vec<DroppedQuestion>,
/// Which lectures to go back to, the one that would repay the most time
/// first.
#[serde(skip_serializing_if = "Vec::is_empty")]
@@ -360,6 +362,17 @@ pub struct QuestionRow {
pub review: Vec<ItemReading>,
}
/// One question thrown out after the exam.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct DroppedQuestion {
/// The recorded question number.
pub number: u32,
/// Whether the drop was applied by crediting every option, in which case the
/// question is still in the points of record.
pub full_credit: bool,
}
/// One citation to read again after missing a question.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "kebab-case")]
@@ -543,13 +556,16 @@ pub fn student(
Vec::new()
};
let mut dropped_questions: Vec<u32> = rows
let mut dropped_questions: Vec<DroppedQuestion> = rows
.iter()
.filter(|r| r.dropped)
.map(|r| r.item_number)
.map(|r| DroppedQuestion {
number: r.item_number,
full_credit: r.dropped_full_credit,
})
.collect();
dropped_questions.sort_unstable();
dropped_questions.dedup();
dropped_questions.sort_by_key(|d| d.number);
dropped_questions.dedup_by_key(|d| d.number);
// Built from the response rows rather than from `questions`, so a report with
// `--no-questions` still says where to go back to; it just does not name the
@@ -952,11 +968,11 @@ pub struct CohortDiagnostic {
pub lectures: Vec<CohortLectureRow>,
/// Per-question statistics.
pub questions: Vec<CohortQuestionRow>,
/// Questions dropped from scoring, which are therefore absent from every
/// table above. Recorded so the report says why rather than leaving a gap in
/// the numbering.
/// Questions thrown out, which are therefore absent from every table above.
/// Recorded so the report says why rather than leaving a gap in the
/// numbering.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub dropped_questions: Vec<u32>,
pub dropped_questions: Vec<DroppedQuestion>,
/// What to do about each question that raised something.
pub triage: Triage,
/// How the authored expectations did.
@@ -1509,12 +1525,16 @@ pub fn cohort(
.filter_map(|item| questions.iter().find(|q| q.number == item.number).cloned())
.collect();
let mut dropped_questions: Vec<u32> = set
let mut dropped_questions: Vec<DroppedQuestion> = set
.all_items()
.into_iter()
.filter(|n| set.item_dropped(*n))
.map(|number| DroppedQuestion {
number,
full_credit: set.for_item(number).iter().any(|r| r.dropped_full_credit),
})
.collect();
dropped_questions.sort_unstable();
dropped_questions.sort_by_key(|d| d.number);
let default_options = course.policy.options_per_item;
let triage = triage(&questions, threshold, default_options);