use crate::each::{BatchOptions, Item, ItemResult, ask_each, validate_ids};
use crate::{Answer, AnswerValue, Boolean, Error, Result, State};
use serde_json::{Map, Value};
use std::collections::BTreeSet;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FilterOutcome {
Match,
No,
Unsure,
Failed,
}
#[derive(Clone, Debug)]
pub struct Verdict {
id: String,
position: usize,
outcome: FilterOutcome,
result: ItemResult,
}
impl Verdict {
pub fn id(&self) -> &str {
&self.id
}
pub fn position(&self) -> usize {
self.position
}
pub fn outcome(&self) -> FilterOutcome {
self.outcome
}
pub fn answer(&self) -> Option<&Answer<AnswerValue>> {
self.result.answer()
}
pub fn error(&self) -> Option<&Error> {
self.result.error()
}
}
#[derive(Clone, Debug)]
pub struct Filtered {
question: Boolean,
verdicts: Vec<Verdict>,
}
impl Filtered {
pub fn question(&self) -> &Boolean {
&self.question
}
pub fn verdicts(&self) -> &[Verdict] {
&self.verdicts
}
pub fn matched(&self) -> Vec<&Verdict> {
self.with_outcome(FilterOutcome::Match)
}
pub fn rejected(&self) -> Vec<&Verdict> {
self.with_outcome(FilterOutcome::No)
}
pub fn unsure(&self) -> Vec<&Verdict> {
self.with_outcome(FilterOutcome::Unsure)
}
pub fn failed(&self) -> Vec<&Verdict> {
self.with_outcome(FilterOutcome::Failed)
}
pub fn models(&self) -> BTreeSet<&str> {
self.verdicts
.iter()
.filter_map(|v| v.answer())
.filter_map(|a| a.call())
.filter_map(|c| c.model.as_deref())
.filter(|m| !m.is_empty())
.collect()
}
fn with_outcome(&self, outcome: FilterOutcome) -> Vec<&Verdict> {
self.verdicts
.iter()
.filter(|v| v.outcome == outcome)
.collect()
}
}
pub async fn filter_items(
provider: &dyn crate::Provider,
question: &Boolean,
items: &[Item],
options: &BatchOptions,
) -> Result<Filtered> {
validate_ids(items.iter().map(Item::id))?;
let mut states = Vec::with_capacity(items.len());
for item in items {
let state = if let Some(context) = options.context.as_ref().filter(|v| !v.is_null()) {
let mut value = Map::new();
value.insert("context".into(), context.clone());
value.insert("item".into(), item.state().as_value().clone());
State::try_from(Value::Object(value))?
} else {
item.state().clone()
};
states.push(state);
}
let results = ask_each(provider, &question.clone().into(), &states, options).await?;
let verdicts = items
.iter()
.zip(results)
.enumerate()
.map(|(position, (item, result))| {
let outcome = match &result {
ItemResult::Error(_) => FilterOutcome::Failed,
ItemResult::Answer(answer) => match answer.value() {
Some(AnswerValue::Boolean(true)) => FilterOutcome::Match,
Some(AnswerValue::Boolean(false)) => FilterOutcome::No,
_ => FilterOutcome::Unsure,
},
};
Verdict {
id: item.id().to_owned(),
position,
outcome,
result,
}
})
.collect();
Ok(Filtered {
question: question.clone(),
verdicts,
})
}