use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvaluationResult {
pub mechanical_pass: bool,
pub semantic_pass: Option<bool>,
pub consensus_pass: Option<bool>,
pub score: f64,
pub notes: Vec<String>,
}
impl EvaluationResult {
pub fn mechanical_only(pass: bool, score: f64) -> Self {
Self {
mechanical_pass: pass,
semantic_pass: None,
consensus_pass: None,
score,
notes: Vec::new(),
}
}
pub fn all_passed(&self) -> bool {
self.mechanical_pass
&& self.semantic_pass.unwrap_or(true)
&& self.consensus_pass.unwrap_or(true)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MechanicalEvalResult {
pub criterion_results: Vec<CriterionResult>,
pub all_passed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CriterionResult {
pub criterion: String,
pub passed: bool,
pub reason: String,
}
impl MechanicalEvalResult {
pub fn evaluate(criteria: &[String], output: &str) -> Self {
let output_lower = output.to_lowercase();
let mut results = Vec::new();
for criterion in criteria {
let c_lower = criterion.to_lowercase();
let (passed, reason) =
if c_lower.contains("exit code") || c_lower.contains("exit status") {
let has_zero = output_lower.contains("exit code 0")
|| output_lower.contains("exit status 0");
(has_zero, format!("exit_code_0={has_zero}"))
} else {
let key_tokens: Vec<&str> = c_lower
.split_whitespace()
.filter(|w| {
w.len() > 3
&& !matches!(
*w,
"must"
| "should"
| "shall"
| "where"
| "which"
| "that"
| "with"
| "from"
| "this"
)
})
.collect();
if key_tokens.is_empty() {
let contains = output_lower.contains(&c_lower);
(contains, format!("substring_match={contains}"))
} else {
let matched = key_tokens
.iter()
.filter(|t| output_lower.contains(*t))
.count();
let ratio = matched as f64 / key_tokens.len() as f64;
let passed = ratio >= 0.5; (
passed,
format!(
"keyword_match={}/{} ({:.0}%)",
matched,
key_tokens.len(),
ratio * 100.0
),
)
}
};
results.push(CriterionResult {
criterion: criterion.clone(),
passed,
reason,
});
}
let all_passed = results.iter().all(|r| r.passed);
Self {
criterion_results: results,
all_passed,
}
}
}