1use aspero_core::{Doi, FieldSet, IdError, ProviderId, SourceRecord, Usage, WorkId};
6
7use crate::enrich::found_records;
8use crate::{Abstract, Disagreement, Engine, LookupReport, LookupResult, add_usage};
9
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
12#[non_exhaustive]
13pub struct Extras {
14 pub full: bool,
18 pub abstracts: bool,
19}
20
21impl Extras {
22 pub fn full() -> Self {
23 Extras {
24 full: true,
25 abstracts: false,
26 }
27 }
28}
29
30#[derive(Debug, Clone)]
32pub enum InputLookup {
33 Invalid(IdError),
35 Looked(Box<Looked>),
36}
37
38#[derive(Debug, Clone)]
39#[non_exhaustive]
40pub struct Looked {
41 pub result: LookupResult,
42 pub with_punctuation: bool,
44 pub disagreements: Vec<Disagreement>,
46 pub latest: Option<SourceRecord>,
48 pub abstract_text: Option<Option<Abstract>>,
51}
52
53#[derive(Debug, Clone)]
54pub struct InputsReport {
55 pub items: Vec<InputLookup>,
57 pub usage: Vec<(ProviderId, Usage)>,
58}
59
60impl Engine {
61 pub async fn lookup_inputs(&self, inputs: &[String], extras: Extras) -> InputsReport {
65 let parsed: Vec<Result<WorkId, IdError>> = inputs.iter().map(|s| s.parse()).collect();
66 let (positions, valid): (Vec<usize>, Vec<WorkId>) = parsed
67 .iter()
68 .enumerate()
69 .filter_map(|(i, p)| Some((i, p.as_ref().ok()?.clone())))
70 .unzip();
71 let mut report = self.lookup(&valid, FieldSet::DEFAULT).await;
72 let retried = self
73 .retry_with_punctuation(inputs, &positions, &mut report)
74 .await;
75
76 let n = report.results.len();
77 let mut disagreements = vec![Vec::new(); n];
78 let mut latest: Vec<Option<SourceRecord>> = vec![None; n];
79 let mut abstracts: Vec<Option<Option<Abstract>>> = vec![None; n];
80 if extras.full {
81 self.add_notices(&mut report).await;
82 self.add_open_access(&mut report).await;
83 disagreements = self.cross_check(&mut report).await;
84 let found: Vec<(usize, &SourceRecord)> =
85 found_records(&report.results, |_| true).collect();
86 let records: Vec<&SourceRecord> = found.iter().map(|(_, r)| *r).collect();
87 let versions = self.latest_versions(&records).await;
88 for ((i, _), record) in found.iter().zip(versions.latest) {
89 latest[*i] = record;
90 }
91 for (p, u) in versions.usage {
92 add_usage(&mut report.usage, &p, u);
93 }
94 }
95 if extras.abstracts {
96 let found: Vec<(usize, &SourceRecord)> =
97 found_records(&report.results, |_| true).collect();
98 let records: Vec<&SourceRecord> = found.iter().map(|(_, r)| *r).collect();
99 let fetched = self.abstracts(&records).await;
100 for ((i, _), a) in found.iter().zip(fetched.found) {
101 abstracts[*i] = Some(a);
102 }
103 for (p, u) in fetched.usage {
104 add_usage(&mut report.usage, &p, u);
105 }
106 }
107
108 let mut looked = report
109 .results
110 .into_iter()
111 .zip(disagreements)
112 .zip(latest)
113 .zip(abstracts)
114 .map(
115 |(((result, disagreements), latest), abstract_text)| Looked {
116 with_punctuation: retried.contains(&result.id),
117 result,
118 disagreements,
119 latest,
120 abstract_text,
121 },
122 );
123 let items = parsed
124 .into_iter()
125 .map(|p| match p {
126 Err(e) => InputLookup::Invalid(e),
127 Ok(_) => InputLookup::Looked(Box::new(
128 looked.next().expect("one result per valid input"),
129 )),
130 })
131 .collect();
132 InputsReport {
133 items,
134 usage: report.usage,
135 }
136 }
137
138 async fn retry_with_punctuation(
143 &self,
144 inputs: &[String],
145 positions: &[usize],
146 report: &mut LookupReport,
147 ) -> Vec<WorkId> {
148 let retries: Vec<(usize, WorkId)> = positions
149 .iter()
150 .zip(&report.results)
151 .enumerate()
152 .filter(|(_, (_, r))| r.is_not_found())
153 .filter_map(|(i, (&at, _))| {
154 Some((i, WorkId::Doi(Doi::with_trailing_punctuation(&inputs[at])?)))
155 })
156 .collect();
157 if retries.is_empty() {
158 return Vec::new();
159 }
160 let ids: Vec<WorkId> = retries.iter().map(|(_, id)| id.clone()).collect();
161 let second = self.lookup(&ids, FieldSet::DEFAULT).await;
162 for (p, u) in second.usage {
163 add_usage(&mut report.usage, &p, u);
164 }
165 let mut found = Vec::new();
166 for ((i, _), result) in retries.into_iter().zip(second.results) {
167 if result.record.is_some() {
168 found.push(result.id.clone());
169 report.results[i] = result;
170 }
171 }
172 found
173 }
174}