use super::*;
pub(super) fn classify(text: &str, facts: &Facts<'_>) -> ClaimAssessment {
let unverified = ClaimAssessment::Unverified("approximate quantity");
static QUALIFIED_NUMBER: LazyLock<regex::Regex> = LazyLock::new(|| {
regex(r"(?i)(?:~|≈|\b(?:about|roughly|approximately|around)\b)\s*([+−-]?\d[\d,]*)")
});
if !QUALIFIED_NUMBER.is_match(text) {
return unverified;
}
let raw_sizes: Vec<_> = SIZE.captures_iter(text).collect();
if raw_sizes.len() != 1 {
return unverified;
}
let numeral = raw_sizes[0].get(1).expect("size numeral");
let qualified = QUALIFIED_NUMBER
.captures_iter(text)
.find(|caps| caps.get(1).is_some_and(|n| n.range() == numeral.range()));
let approximate_size = qualified.is_some();
let mut normalized = text.to_owned();
if let Some(caps) = qualified {
normalized.replace_range(caps.get(0).expect("qualified numeral").range(), &caps[1]);
}
let text = normalized;
if CHANGE.is_match(&text)
|| COMPARISON.is_match(&text)
|| NET.is_match(&text)
|| PAIR.is_match(&text)
{
return unverified;
}
let paths: Vec<_> = PATH.find_iter(&text).map(|m| m.as_str()).collect();
let sizes: Vec<_> = SIZE.captures_iter(&text).collect();
if paths.len() != 1 || sizes.len() != 1 {
return unverified;
}
let Some(expected) = number(&sizes[0][1]) else {
return unverified;
};
let path = regex::escape(paths[0]);
let absolute = regex(&format!(
r"(?i){path}\s*(?::|is(?:\s+now)?|now|after|before)\s*:?\s*[\d,]+\s+lines\b"
));
let descriptor = regex(&format!(r"(?i){path}\s*\(\s*[\d,]+\s+lines\)"));
if !absolute.is_match(&text) && !descriptor.is_match(&text) {
return unverified;
}
let residue = text.replacen(&sizes[0][0], "", 1);
if QUANTITY.is_match(&residue) {
return unverified;
}
let Some(subject) = super::super::super::files::resolve(facts.before, facts.after, paths[0])
else {
return unverified;
};
let before = facts.before.and_then(|s| s.lines(&subject));
let after = facts.after.and_then(|s| s.lines(&subject));
static BEFORE: LazyLock<regex::Regex> =
LazyLock::new(|| regex(r"(?i)\b(?:before|original|originally)\b"));
static AFTER: LazyLock<regex::Regex> = LazyLock::new(|| regex(r"(?i)\b(?:after|now)\b"));
let scope = text.replacen(paths[0], "", 1);
let original = BEFORE.is_match(&scope);
let current = AFTER.is_match(&scope) || (!original && regex(r"(?i)\bis\b").is_match(&scope));
if original && current {
return unverified;
}
let values: Vec<_> = if original {
before.into_iter().collect()
} else if current {
after.into_iter().collect()
} else {
before.into_iter().chain(after).collect()
};
if values.is_empty() {
return unverified;
}
if values.iter().any(|&actual| {
expected == actual
|| (approximate_size
&& (i128::from(expected) - i128::from(actual)).abs()
* *super::super::APPROXIMATION_DIVISOR
<= i128::from(actual).abs())
}) {
return ClaimAssessment::Correct;
}
let observed = if original {
format!("{subject}: {} lines before", values[0])
} else if !current {
match (before, after) {
(Some(before), Some(after)) if before != after => {
format!("{subject}: {before} → {after} lines")
}
_ => format!("{subject}: {} lines", values[0]),
}
} else {
format!("{subject}: {} lines", values[0])
};
ClaimAssessment::Corrected(observed)
}