use crate::error::EvaluationError;
use crate::evaluate::types::{
ComparisonResults, EvalResults, MissingTask, TaskComparison, WorkflowComparison,
};
use std::collections::HashMap;
pub fn compare_results(
baseline: &EvalResults,
comparison: &EvalResults,
regression_threshold: f64,
) -> Result<ComparisonResults, EvaluationError> {
let baseline_map: HashMap<_, _> = baseline
.aligned_results
.iter()
.filter(|r| r.success)
.map(|r| {
if r.record_id.is_empty() {
(r.record_uid.as_str(), r)
} else {
(r.record_id.as_str(), r)
}
})
.collect();
let comparison_map: HashMap<_, _> = comparison
.aligned_results
.iter()
.filter(|r| r.success)
.map(|r| {
if r.record_id.is_empty() {
(r.record_uid.as_str(), r)
} else {
(r.record_id.as_str(), r)
}
})
.collect();
let mut workflow_comparisons = Vec::new();
let mut task_status_changes = Vec::new();
let mut missing_tasks = Vec::new();
for (record_id, baseline_result) in &baseline_map {
if let Some(comparison_result) = comparison_map.get(record_id) {
let baseline_task_map: HashMap<_, _> = baseline_result
.eval_set
.records
.iter()
.map(|t| (t.task_id.as_str(), t))
.collect();
let comparison_task_map: HashMap<_, _> = comparison_result
.eval_set
.records
.iter()
.map(|t| (t.task_id.as_str(), t))
.collect();
let mut workflow_task_comparisons = Vec::new();
let mut matched_baseline_passed = 0;
let mut matched_comparison_passed = 0;
let mut total_matched = 0;
for (task_id, baseline_task) in &baseline_task_map {
if let Some(comparison_task) = comparison_task_map.get(task_id) {
let status_changed = baseline_task.passed != comparison_task.passed;
if baseline_task.passed {
matched_baseline_passed += 1;
}
if comparison_task.passed {
matched_comparison_passed += 1;
}
total_matched += 1;
let task_comp = TaskComparison {
task_id: task_id.to_string(),
baseline_passed: baseline_task.passed,
comparison_passed: comparison_task.passed,
status_changed,
record_id: (*record_id).to_string(),
};
workflow_task_comparisons.push(task_comp.clone());
if status_changed {
task_status_changes.push(task_comp.clone());
}
} else {
missing_tasks.push(MissingTask {
task_id: task_id.to_string(),
present_in: "baseline_only".to_string(),
record_id: (*record_id).to_string(),
});
}
}
for task_id in comparison_task_map.keys() {
if !baseline_task_map.contains_key(task_id) {
missing_tasks.push(MissingTask {
task_id: task_id.to_string(),
present_in: "comparison_only".to_string(),
record_id: (*record_id).to_string(),
});
}
}
let baseline_pass_rate = if total_matched > 0 {
matched_baseline_passed as f64 / total_matched as f64
} else {
0.0
};
let comparison_pass_rate = if total_matched > 0 {
matched_comparison_passed as f64 / total_matched as f64
} else {
0.0
};
let pass_rate_delta = comparison_pass_rate - baseline_pass_rate;
let is_regression = pass_rate_delta < -regression_threshold;
workflow_comparisons.push(WorkflowComparison {
baseline_id: (*record_id).to_string(),
comparison_id: (*record_id).to_string(),
baseline_pass_rate,
comparison_pass_rate,
pass_rate_delta,
is_regression,
task_comparisons: workflow_task_comparisons,
});
}
}
let (improved, regressed, unchanged) =
workflow_comparisons
.iter()
.fold((0, 0, 0), |(i, r, u), wc| {
if wc.is_regression {
(i, r + 1, u)
} else if wc.pass_rate_delta > 0.01 {
(i + 1, r, u)
} else {
(i, r, u + 1)
}
});
let mean_delta = if !workflow_comparisons.is_empty() {
workflow_comparisons
.iter()
.map(|wc| wc.pass_rate_delta)
.sum::<f64>()
/ workflow_comparisons.len() as f64
} else {
0.0
};
let has_regressed = regressed > 0;
Ok(ComparisonResults {
workflow_comparisons,
total_workflows: improved + regressed + unchanged,
improved_workflows: improved,
regressed_workflows: regressed,
unchanged_workflows: unchanged,
mean_pass_rate_delta: mean_delta,
task_status_changes,
missing_tasks,
baseline_workflow_count: baseline.aligned_results.len(),
comparison_workflow_count: comparison.aligned_results.len(),
regressed: has_regressed,
})
}