feat: cooked up something fierce
This commit is contained in:
@@ -134,6 +134,14 @@ pub struct ItemAnalysis {
|
||||
pub number: u32,
|
||||
/// The item's global id, when known.
|
||||
pub item_ref: Option<String>,
|
||||
/// The option set administered, when the rows agree on one.
|
||||
///
|
||||
/// Within one administration an item has one variant, because a placement's
|
||||
/// distractors are drawn once and shared by every form — only the printed
|
||||
/// order differs. `None` means the rows disagreed, which happens when a
|
||||
/// course opts into drawing distractors per form; the statistics below then
|
||||
/// describe a mixture and cannot be pooled by option set.
|
||||
pub variant: Option<String>,
|
||||
/// How many students the item was administered to.
|
||||
pub n: usize,
|
||||
/// How many gave a non-blank response.
|
||||
@@ -553,6 +561,7 @@ pub fn analyze(
|
||||
item_ref: record
|
||||
.and_then(|r| r.placement(*number))
|
||||
.map(|p| p.item.clone()),
|
||||
variant: one_variant(set, *number),
|
||||
n,
|
||||
n_answered,
|
||||
blank_rate: blank as f64 / responded as f64,
|
||||
@@ -906,6 +915,25 @@ fn reliability(coded: &[Vec<f64>], totals: &[f64], p_values: &[f64], rpbs: &[f64
|
||||
/// # Returns
|
||||
///
|
||||
/// The letters that appear on full-credit responses.
|
||||
/// The single variant every row for one question names, if they agree.
|
||||
///
|
||||
/// Disagreement is not an error, it is a fact about the administration: a course
|
||||
/// that draws distractors per form has two option sets under one question
|
||||
/// number, and no pooled statistic describes both. Returning `None` is what
|
||||
/// keeps the per-variant records from claiming otherwise.
|
||||
fn one_variant(set: &ResponseSet, number: u32) -> Option<String> {
|
||||
let mut seen: Option<&str> = None;
|
||||
for row in set.rows.iter().filter(|r| r.item_number == number) {
|
||||
let variant = row.variant.as_deref()?;
|
||||
match seen {
|
||||
None => seen = Some(variant),
|
||||
Some(first) if first == variant => {}
|
||||
Some(_) => return None,
|
||||
}
|
||||
}
|
||||
seen.map(str::to_string)
|
||||
}
|
||||
|
||||
fn infer_key(rows: &[&crate::responses::Response]) -> Vec<String> {
|
||||
let mut out: BTreeSet<String> = BTreeSet::new();
|
||||
for r in rows {
|
||||
@@ -1001,6 +1029,7 @@ mod tests {
|
||||
item_number: number,
|
||||
item_ref: None,
|
||||
item_version: None,
|
||||
variant: None,
|
||||
selected: if letter.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user