feat: improve dropped question support
This commit is contained in:
@@ -396,6 +396,39 @@ pub fn analyze(
|
||||
));
|
||||
}
|
||||
|
||||
// A drop changes every student's percentage, so the two ways to get it wrong
|
||||
// are worth saying out loud. Both are silent otherwise: the numbers simply
|
||||
// come out different from the platform's.
|
||||
if let Some(record) = record {
|
||||
for placement in record.items.iter().filter(|p| p.dropped) {
|
||||
let rows = set.for_item(placement.number);
|
||||
if rows.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let all_credited = rows.iter().all(|r| r.credit >= 0.999);
|
||||
if placement.dropped_with_credit() && !all_credited {
|
||||
let short = rows.iter().filter(|r| r.credit < 0.999).count();
|
||||
warnings.push(format!(
|
||||
"question {} is marked `dropped_as: full_credit`, but {short} of {} responses \
|
||||
carry less than full credit. Either the platform was not regraded or the \
|
||||
export predates the regrade; until one of those is fixed this report's \
|
||||
percentages will sit below the grade of record",
|
||||
placement.number,
|
||||
rows.len()
|
||||
));
|
||||
}
|
||||
if !placement.dropped_with_credit() && all_credited {
|
||||
warnings.push(format!(
|
||||
"question {} is dropped and every response carries full credit, which is what \
|
||||
crediting every option on the platform looks like. It is being removed from \
|
||||
the denominator here, so this report will read slightly lower than the \
|
||||
platform. Set `dropped_as: full_credit` if the platform kept the point",
|
||||
placement.number
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
let mut p_values = Vec::new();
|
||||
let mut rpbs = Vec::new();
|
||||
@@ -986,6 +1019,7 @@ mod tests {
|
||||
topics: vec![],
|
||||
bonus: false,
|
||||
dropped: false,
|
||||
dropped_full_credit: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+33
-13
@@ -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);
|
||||
|
||||
@@ -1112,6 +1112,7 @@ mod tests {
|
||||
topics: vec![],
|
||||
bonus: false,
|
||||
dropped: false,
|
||||
dropped_full_credit: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user