use serde_json::Value;
pub const ON_TASK_FLOOR: f64 = 0.5;
pub const DUE_BACKLOG_AT: usize = 40;
#[must_use]
pub fn on_task(rows: &[Value], cue: &str) -> (Vec<Value>, usize) {
let texts: Vec<&str> = rows
.iter()
.map(|row| row["text"].as_str().unwrap_or(""))
.collect();
let scores = crate::lexical_match_scores(&texts, cue);
let kept: Vec<Value> = rows
.iter()
.zip(&scores)
.filter(|(_, score)| **score >= ON_TASK_FLOOR)
.map(|(row, _)| row.clone())
.collect();
let left_out = rows.len() - kept.len();
(kept, left_out)
}
#[must_use]
pub fn left_out_line(left_out: usize) -> String {
if left_out == 0 {
return String::new();
}
format!(
"({left_out} activated {} not on this task left out; `ljos island \"...\"` prints the whole cluster)\n",
if left_out == 1 { "memory" } else { "memories" }
)
}
#[must_use]
pub fn due_rows_shown(due: usize, cap: usize) -> usize {
if due >= DUE_BACKLOG_AT {
0
} else {
due.min(cap)
}
}
#[must_use]
pub fn backlog_line(due: usize, cap: usize) -> String {
if due_rows_shown(due, cap) > 0 || due == 0 {
return String::new();
}
format!("{due} reviews are due, too many to read here; `ljos due` shows the soonest {cap} to grade\n")
}
#[cfg(test)]
mod tests {
use super::*;
fn row(text: &str) -> Value {
serde_json::json!({ "id": text, "text": text })
}
#[test]
fn a_sitting_keeps_the_rows_about_its_own_task() {
let rows = [
row("The seat guard refuses a read of seats.toml, so adding a seat needs a hand edit."),
row("The disk audit found 40 GB of old encoder downloads under the cache."),
row("Pocket credentials live in the password manager, never in a repository."),
];
let (kept, left_out) = on_task(&rows, "seat guard refuses reading seats.toml");
let texts: Vec<&str> = kept.iter().map(|r| r["text"].as_str().unwrap()).collect();
assert_eq!(texts.len(), 1, "{texts:?}");
assert!(texts[0].contains("seat guard"), "{texts:?}");
assert_eq!(left_out, 2);
assert!(left_out_line(left_out).contains("2 activated memories"));
}
#[test]
fn nothing_is_said_when_every_row_bears_on_the_task() {
let rows = [row("The tracker writes go to the configured tracker root.")];
let (kept, left_out) = on_task(&rows, "tracker root configured for writes");
assert_eq!(kept.len(), 1);
assert_eq!(left_out, 0);
assert_eq!(left_out_line(left_out), "");
}
#[test]
fn a_backlog_prints_its_count_and_no_rows() {
assert_eq!(due_rows_shown(9549, 8), 0);
let line = backlog_line(9549, 8);
assert!(line.contains("9549 reviews are due"), "{line}");
assert!(line.contains("ljos due"), "{line}");
}
#[test]
fn a_short_list_is_still_printed_to_grade() {
assert_eq!(due_rows_shown(3, 8), 3);
assert_eq!(due_rows_shown(12, 8), 8);
assert_eq!(backlog_line(3, 8), "");
assert_eq!(backlog_line(0, 8), "");
}
}