use crate::error::FoldError;
use crate::objective::{Objective, ObjectiveContext};
use crate::selector::{Selector, SelectorInput, SelectorOutput, SelectorWeights};
pub struct ComposePipeline<T> {
pub objective: Box<dyn Objective<T>>,
pub selector: Box<dyn Selector<T>>,
}
impl<T: Clone + Send + Sync + 'static> ComposePipeline<T> {
pub fn execute(
&self,
candidates: Vec<SelectorInput<T>>,
budget: usize,
weights: &SelectorWeights,
context: &ObjectiveContext,
) -> Result<SelectorOutput<T>, FoldError> {
let considered = context
.max_candidates
.unwrap_or(candidates.len())
.min(candidates.len());
let mut scored = Vec::with_capacity(considered);
for mut candidate in candidates.into_iter().take(considered) {
let score = self.objective.score(&candidate.content, context);
if !self.objective.passes_score(score, context) {
continue;
}
let precision = self.objective.precision(&candidate.content, context);
let precision = if precision.is_finite() {
precision
} else {
1.0
};
let effective = score * precision;
if !effective.is_finite() || effective < f32::MIN as f64 || effective > f32::MAX as f64
{
return Err(FoldError::InvalidInput(format!(
"objective effective score for '{}' is outside finite f32 range",
candidate.id
)));
}
candidate.score = effective as f32;
candidate.rank_score = Some(effective);
scored.push(candidate);
}
self.selector.select(scored, budget, weights)
}
}
#[cfg(test)]
#[path = "pipeline_tests.rs"]
mod tests;