use std::fmt::Write as _;
use crate::{EvalCaseResult, EvalMetricResult, EvalSetResult, Verdict};
use super::{Reporter, ReporterError, ReporterOutput};
#[derive(Debug, Default, Clone, Copy)]
pub struct ConsoleReporter;
impl ConsoleReporter {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl Reporter for ConsoleReporter {
fn render(&self, result: &EvalSetResult) -> Result<ReporterOutput, ReporterError> {
let mut out = String::new();
writeln!(
out,
"Eval set: {id} ({passed}/{total} passed)",
id = result.eval_set_id,
passed = result.summary.passed,
total = result.summary.total_cases,
)
.map_err(|e| ReporterError::Format(e.to_string()))?;
for case in &result.case_results {
write_case_line(&mut out, case)?;
for metric in &case.metric_results {
write_metric_line(&mut out, metric)?;
}
}
writeln!(
out,
"Summary: {passed} passed, {failed} failed, total_cost=${cost:.6}, duration={dur}ms",
passed = result.summary.passed,
failed = result.summary.failed,
cost = result.summary.total_cost.total,
dur = result.summary.total_duration.as_millis(),
)
.map_err(|e| ReporterError::Format(e.to_string()))?;
Ok(ReporterOutput::Stdout(out))
}
}
fn write_case_line(out: &mut String, case: &EvalCaseResult) -> Result<(), ReporterError> {
writeln!(
out,
"- {id} {verdict} ({dur}ms)",
id = case.case_id,
verdict = verdict_label(case.verdict),
dur = case.invocation.total_duration.as_millis(),
)
.map_err(|e| ReporterError::Format(e.to_string()))
}
fn write_metric_line(out: &mut String, metric: &EvalMetricResult) -> Result<(), ReporterError> {
let verdict = metric.score.verdict();
write!(
out,
" {name} score={score:.2} threshold={th:.2} {verdict}",
name = metric.evaluator_name,
score = metric.score.value,
th = metric.score.threshold,
verdict = verdict_label(verdict),
)
.map_err(|e| ReporterError::Format(e.to_string()))?;
if let Some(details) = metric.details.as_ref().filter(|s| !s.is_empty()) {
let sanitized = details.replace(['\n', '\r'], " ");
write!(out, " reason: {sanitized}").map_err(|e| ReporterError::Format(e.to_string()))?;
}
writeln!(out).map_err(|e| ReporterError::Format(e.to_string()))?;
Ok(())
}
const fn verdict_label(v: Verdict) -> &'static str {
match v {
Verdict::Pass => "PASS",
Verdict::Fail => "FAIL",
}
}