aspero_core/matching/
derivative.rs1use super::Claim;
5use super::text::normalise;
6use crate::id::starts_with_ci;
7
8pub(super) const DERIVATIVE_MARKERS: &[&str] = &[
11 "reply",
12 "erratum",
13 "errata",
14 "corrigendum",
15 "correction",
16 "comment on",
17 "response to",
18 "in response",
19 "faculty opinions recommendation",
20 "retraction of",
21 "expression of concern",
22];
23
24const LEADING_MARKERS: &[&str] = &["comment on", "response to", "in response", "correction"];
29
30pub fn is_derivative(title: &str) -> bool {
33 derivative_kind(title).is_some()
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38#[non_exhaustive]
39pub enum WorkKind {
40 Reply,
41 Correction,
42 Comment,
43 Retraction,
44 ExpressionOfConcern,
45 Recommendation,
46}
47
48impl WorkKind {
49 pub fn as_str(self) -> &'static str {
52 match self {
53 WorkKind::Reply => "reply",
54 WorkKind::Correction => "correction",
55 WorkKind::Comment => "comment",
56 WorkKind::Retraction => "retraction",
57 WorkKind::ExpressionOfConcern => "expression of concern",
58 WorkKind::Recommendation => "recommendation",
59 }
60 }
61}
62
63pub fn derivative_kind(title: &str) -> Option<&'static str> {
65 derivative_work_kind(title).map(WorkKind::as_str)
66}
67
68pub fn derivative_work_kind(title: &str) -> Option<WorkKind> {
70 let norm = normalise(title);
71 let words = format!(" {norm} ");
72 let leading = |m: &str| match m {
73 "correction" => {
74 norm == "correction"
75 || norm.starts_with("correction to ")
76 || starts_with_ci(title.trim_start(), "correction:")
77 }
78 _ => words.starts_with(&format!(" {m} ")),
79 };
80 let marker = DERIVATIVE_MARKERS
81 .iter()
82 .find(|m| match LEADING_MARKERS.contains(m) {
83 true => leading(m),
84 false => words.contains(&format!(" {m} ")),
85 })?;
86 Some(match *marker {
87 "reply" | "response to" | "in response" => WorkKind::Reply,
88 "erratum" | "errata" | "corrigendum" | "correction" => WorkKind::Correction,
89 "comment on" => WorkKind::Comment,
90 "retraction of" => WorkKind::Retraction,
91 "expression of concern" => WorkKind::ExpressionOfConcern,
92 _ => WorkKind::Recommendation,
93 })
94}
95
96pub fn claim_mentions_derivative(claim: &Claim) -> bool {
100 claim.title.as_deref().is_some_and(is_derivative)
101 || claim
102 .text
103 .as_deref()
104 .is_some_and(|text| text.split(['.', '?', '!']).any(is_derivative))
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 fn text(t: &str) -> Claim {
112 Claim {
113 text: Some(t.into()),
114 ..Claim::default()
115 }
116 }
117
118 #[test]
119 fn claims_mention_derivatives_as_records_do() {
120 assert!(claim_mentions_derivative(&text(
121 "Smith J. Correction to: Deep learning. Nature. 2016."
122 )));
123 assert!(claim_mentions_derivative(&text(
124 "LeCun Y. Deep learning—Reply. Nature."
125 )));
126 assert!(!claim_mentions_derivative(&text(
128 "Smith J. Differential response to corticosteroids. Thorax. 2019."
129 )));
130 let title = Claim {
131 title: Some("Correction of hyponatraemia".into()),
132 ..Claim::default()
133 };
134 assert!(!claim_mentions_derivative(&title));
135 }
136}