use aspero_core::{Doi, FieldSet, IdError, ProviderId, SourceRecord, Usage, WorkId};
use crate::enrich::found_records;
use crate::{Abstract, Disagreement, Engine, LookupReport, LookupResult, add_usage};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct Extras {
pub full: bool,
pub abstracts: bool,
}
impl Extras {
pub fn full() -> Self {
Extras {
full: true,
abstracts: false,
}
}
}
#[derive(Debug, Clone)]
pub enum InputLookup {
Invalid(IdError),
Looked(Box<Looked>),
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Looked {
pub result: LookupResult,
pub with_punctuation: bool,
pub disagreements: Vec<Disagreement>,
pub latest: Option<SourceRecord>,
pub abstract_text: Option<Option<Abstract>>,
}
#[derive(Debug, Clone)]
pub struct InputsReport {
pub items: Vec<InputLookup>,
pub usage: Vec<(ProviderId, Usage)>,
}
impl Engine {
pub async fn lookup_inputs(&self, inputs: &[String], extras: Extras) -> InputsReport {
let parsed: Vec<Result<WorkId, IdError>> = inputs.iter().map(|s| s.parse()).collect();
let (positions, valid): (Vec<usize>, Vec<WorkId>) = parsed
.iter()
.enumerate()
.filter_map(|(i, p)| Some((i, p.as_ref().ok()?.clone())))
.unzip();
let mut report = self.lookup(&valid, FieldSet::DEFAULT).await;
let retried = self
.retry_with_punctuation(inputs, &positions, &mut report)
.await;
let n = report.results.len();
let mut disagreements = vec![Vec::new(); n];
let mut latest: Vec<Option<SourceRecord>> = vec![None; n];
let mut abstracts: Vec<Option<Option<Abstract>>> = vec![None; n];
if extras.full {
self.add_notices(&mut report).await;
self.add_open_access(&mut report).await;
disagreements = self.cross_check(&mut report).await;
let found: Vec<(usize, &SourceRecord)> =
found_records(&report.results, |_| true).collect();
let records: Vec<&SourceRecord> = found.iter().map(|(_, r)| *r).collect();
let versions = self.latest_versions(&records).await;
for ((i, _), record) in found.iter().zip(versions.latest) {
latest[*i] = record;
}
for (p, u) in versions.usage {
add_usage(&mut report.usage, &p, u);
}
}
if extras.abstracts {
let found: Vec<(usize, &SourceRecord)> =
found_records(&report.results, |_| true).collect();
let records: Vec<&SourceRecord> = found.iter().map(|(_, r)| *r).collect();
let fetched = self.abstracts(&records).await;
for ((i, _), a) in found.iter().zip(fetched.found) {
abstracts[*i] = Some(a);
}
for (p, u) in fetched.usage {
add_usage(&mut report.usage, &p, u);
}
}
let mut looked = report
.results
.into_iter()
.zip(disagreements)
.zip(latest)
.zip(abstracts)
.map(
|(((result, disagreements), latest), abstract_text)| Looked {
with_punctuation: retried.contains(&result.id),
result,
disagreements,
latest,
abstract_text,
},
);
let items = parsed
.into_iter()
.map(|p| match p {
Err(e) => InputLookup::Invalid(e),
Ok(_) => InputLookup::Looked(Box::new(
looked.next().expect("one result per valid input"),
)),
})
.collect();
InputsReport {
items,
usage: report.usage,
}
}
async fn retry_with_punctuation(
&self,
inputs: &[String],
positions: &[usize],
report: &mut LookupReport,
) -> Vec<WorkId> {
let retries: Vec<(usize, WorkId)> = positions
.iter()
.zip(&report.results)
.enumerate()
.filter(|(_, (_, r))| r.is_not_found())
.filter_map(|(i, (&at, _))| {
Some((i, WorkId::Doi(Doi::with_trailing_punctuation(&inputs[at])?)))
})
.collect();
if retries.is_empty() {
return Vec::new();
}
let ids: Vec<WorkId> = retries.iter().map(|(_, id)| id.clone()).collect();
let second = self.lookup(&ids, FieldSet::DEFAULT).await;
for (p, u) in second.usage {
add_usage(&mut report.usage, &p, u);
}
let mut found = Vec::new();
for ((i, _), result) in retries.into_iter().zip(second.results) {
if result.record.is_some() {
found.push(result.id.clone());
report.results[i] = result;
}
}
found
}
}