use crate::eval::{EvalCase, EvalReport, EvalRunner};
pub struct AbComparison {
pub baseline: EvalReport,
pub experiment: EvalReport,
pub per_case_deltas: Vec<CaseDelta>,
pub improved: usize,
pub regressed: usize,
pub unchanged: usize,
}
pub struct CaseDelta {
pub case_id: String,
pub baseline_score: f64,
pub experiment_score: f64,
pub delta: f64,
pub improved: bool,
}
pub struct AbComparator;
impl AbComparator {
pub async fn compare(
cases: &[EvalCase],
mut baseline_factory: impl FnMut() -> Box<dyn crate::agent::Agent>,
mut experiment_factory: impl FnMut() -> Box<dyn crate::agent::Agent>,
) -> AbComparison {
let workspace = std::env::temp_dir().join(format!("ab_compare_{}", uuid::Uuid::new_v4()));
let runner = EvalRunner::new(workspace);
let baseline = runner.run_all(cases, &mut baseline_factory).await;
let experiment = runner.run_all(cases, &mut experiment_factory).await;
let mut per_case_deltas = Vec::new();
let mut improved = 0usize;
let mut regressed = 0usize;
let mut unchanged = 0usize;
for (base, exp) in baseline.results.iter().zip(experiment.results.iter()) {
let delta = exp.score - base.score;
let is_improved = delta > 0.001;
let is_regressed = delta < -0.001;
if is_improved {
improved += 1;
} else if is_regressed {
regressed += 1;
} else {
unchanged += 1;
}
per_case_deltas.push(CaseDelta {
case_id: base.case_id.clone(),
baseline_score: base.score,
experiment_score: exp.score,
delta,
improved: is_improved,
});
}
let mut experiment = experiment;
experiment.delta_vs_baseline = Some(experiment.avg_score - baseline.avg_score);
AbComparison {
baseline,
experiment,
per_case_deltas,
improved,
regressed,
unchanged,
}
}
pub fn format_summary(comparison: &AbComparison) -> String {
let delta = comparison.experiment.avg_score - comparison.baseline.avg_score;
let direction = if delta > 0.0 {
"improved"
} else if delta < 0.0 {
"regressed"
} else {
"unchanged"
};
let mut lines = vec![
format!("A/B Comparison Results:"),
format!(
" Baseline: {:.4} avg (n={})",
comparison.baseline.avg_score, comparison.baseline.total
),
format!(
" Experiment: {:.4} avg (n={})",
comparison.experiment.avg_score, comparison.experiment.total
),
format!(" Delta: {delta:+.4} → {direction}"),
format!(
" Improved: {} Regressed: {} Unchanged: {}",
comparison.improved, comparison.regressed, comparison.unchanged
),
String::new(),
"Per-case deltas:".into(),
];
for cd in &comparison.per_case_deltas {
let icon = if cd.improved {
"↑"
} else if cd.delta < -0.001 {
"↓"
} else {
"="
};
lines.push(format!(
" {icon} {}: {:+.4} ({:.4} → {:.4})",
cd.case_id, cd.delta, cd.baseline_score, cd.experiment_score
));
}
lines.join("\n")
}
}