use serde_json::json;
use crate::score::EvalResult;
pub fn eval_result_to_outcome(result: &EvalResult) -> serde_json::Value {
let mut score_map = serde_json::Map::new();
let mut violations = Vec::new();
for score in &result.scores {
score_map.insert(
score.evaluator.clone(),
serde_json::Value::from(score.value),
);
if score.value < 0.3 {
violations.push(format!(
"{}: {:.2} (critical, layer: {})",
score.evaluator, score.value, score.layer
));
}
}
let aggregate = result.aggregate_score();
score_map.insert("aggregate".to_string(), serde_json::Value::from(aggregate));
let constraints_passed = violations.is_empty();
json!({
"score": score_map,
"constraints_passed": constraints_passed,
"constraint_violations": violations,
"evaluator_metadata": {
"evaluator": result.evaluator,
"duration_ms": result.duration_ms,
"score_count": result.scores.len(),
"timestamp_ms": result.timestamp_ms,
}
})
}
pub fn eval_result_to_trial_event(
result: &EvalResult,
session_id: &str,
trial_id: Option<&str>,
) -> serde_json::Value {
json!({
"event_type": "eval.egri_outcome",
"session_id": session_id,
"trial_id": trial_id,
"outcome": eval_result_to_outcome(result),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::score::{EvalResult, EvalScore};
use crate::taxonomy::{EvalLayer, EvalTiming};
fn make_result(scores: Vec<(f64, &str)>) -> EvalResult {
EvalResult {
evaluator: "test_evaluator".into(),
scores: scores
.into_iter()
.map(|(value, name)| {
EvalScore::new(name, value, EvalLayer::Reasoning, EvalTiming::Async, "s")
.unwrap()
})
.collect(),
timestamp_ms: 1000,
duration_ms: 42,
}
}
#[test]
fn outcome_has_correct_structure() {
let result = make_result(vec![(0.9, "coherence"), (0.8, "completeness")]);
let outcome = eval_result_to_outcome(&result);
assert!(outcome.get("score").is_some());
assert!(outcome.get("constraints_passed").is_some());
assert!(outcome.get("constraint_violations").is_some());
assert!(outcome.get("evaluator_metadata").is_some());
}
#[test]
fn outcome_score_is_vector() {
let result = make_result(vec![(0.9, "coherence"), (0.8, "completeness")]);
let outcome = eval_result_to_outcome(&result);
let score = outcome.get("score").unwrap().as_object().unwrap();
assert!((score["coherence"].as_f64().unwrap() - 0.9).abs() < f64::EPSILON);
assert!((score["completeness"].as_f64().unwrap() - 0.8).abs() < f64::EPSILON);
assert!((score["aggregate"].as_f64().unwrap() - 0.85).abs() < f64::EPSILON);
}
#[test]
fn critical_scores_become_violations() {
let result = make_result(vec![(0.2, "safety"), (0.9, "coherence")]);
let outcome = eval_result_to_outcome(&result);
assert!(!outcome["constraints_passed"].as_bool().unwrap());
let violations = outcome["constraint_violations"].as_array().unwrap();
assert_eq!(violations.len(), 1);
assert!(violations[0].as_str().unwrap().contains("safety"));
}
#[test]
fn all_good_scores_pass_constraints() {
let result = make_result(vec![(0.9, "a"), (0.8, "b"), (0.7, "c")]);
let outcome = eval_result_to_outcome(&result);
assert!(outcome["constraints_passed"].as_bool().unwrap());
assert!(
outcome["constraint_violations"]
.as_array()
.unwrap()
.is_empty()
);
}
#[test]
fn empty_result_produces_valid_outcome() {
let result = EvalResult {
evaluator: "empty".into(),
scores: vec![],
timestamp_ms: 0,
duration_ms: 0,
};
let outcome = eval_result_to_outcome(&result);
assert!(outcome["constraints_passed"].as_bool().unwrap());
let score = outcome.get("score").unwrap().as_object().unwrap();
assert!((score["aggregate"].as_f64().unwrap()).abs() < f64::EPSILON);
}
#[test]
fn trial_event_has_correct_structure() {
let result = make_result(vec![(0.85, "quality")]);
let event = eval_result_to_trial_event(&result, "sess-1", Some("trial-001"));
assert_eq!(event["event_type"], "eval.egri_outcome");
assert_eq!(event["session_id"], "sess-1");
assert_eq!(event["trial_id"], "trial-001");
assert!(event.get("outcome").is_some());
}
#[test]
fn trial_event_without_trial_id() {
let result = make_result(vec![(0.85, "quality")]);
let event = eval_result_to_trial_event(&result, "sess-1", None);
assert!(event["trial_id"].is_null());
}
#[test]
fn metadata_includes_evaluator_info() {
let result = make_result(vec![(0.8, "test")]);
let outcome = eval_result_to_outcome(&result);
let meta = outcome.get("evaluator_metadata").unwrap();
assert_eq!(meta["evaluator"], "test_evaluator");
assert_eq!(meta["duration_ms"], 42);
assert_eq!(meta["score_count"], 1);
}
}