use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::ids::TaskId;
use super::operator::ComparisonOperator;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum EvalContextCapture {
Full,
Hash,
Redact,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct AssertionResult {
pub task_id: TaskId,
pub passed: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actual: Option<serde_json::Value>,
pub expected: serde_json::Value,
pub operator: ComparisonOperator,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(schema_with = "crate::schema::skipped_option::<String>")]
pub message: Option<String>,
pub stage: u32,
pub started_at: DateTime<Utc>,
pub duration_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum EvalPassGate {
OverallPassRate {
threshold: f64,
},
PerJudgePassRate {
threshold: f64,
},
AllPass,
}
impl EvalPassGate {
pub fn validate(&self) -> Result<(), crate::error::WyrdError> {
let threshold = match self {
Self::OverallPassRate { threshold } | Self::PerJudgePassRate { threshold } => {
*threshold
}
Self::AllPass => return Ok(()),
};
if !(0.0..=1.0).contains(&threshold) || threshold.is_nan() {
return Err(crate::error::WyrdError::Validation {
message: format!("eval_pass_gate threshold must be in [0.0, 1.0]; got {threshold}"),
details: serde_json::Value::Null,
});
}
Ok(())
}
}