Skip to main content

vtcode_eval/
report.rs

1use crate::{EvalMetric, task::EvalCategory};
2use serde::Serialize;
3
4#[derive(Debug, Clone, Serialize)]
5pub struct TaskReport {
6    pub task_id: String,
7    pub category: String,
8    pub metric: EvalMetric,
9}
10
11#[derive(Debug, Clone, Serialize)]
12pub struct SuiteReport {
13    pub suite_id: String,
14    pub suite_name: String,
15    pub task_reports: Vec<TaskReport>,
16    pub aggregate: EvalMetric,
17    pub capability_metrics: EvalMetric,
18    pub regression_metrics: EvalMetric,
19}
20
21#[derive(Debug, Clone, Serialize)]
22pub struct EvalReport {
23    pub generated_at: String,
24    pub suites: Vec<SuiteReport>,
25}
26
27impl EvalReport {
28    pub fn to_markdown(&self) -> String {
29        let mut out = String::new();
30        out.push_str("# Eval Report\n\n");
31        for s in &self.suites {
32            out.push_str(&format!("## {}\n\n", s.suite_name));
33            out.push_str(&format!(
34                "- Aggregate: pass@k={:.1}%\n",
35                s.aggregate.pass_at_k * 100.0
36            ));
37            out.push_str("| Task | Category | pass@k | passed/total |\n");
38            out.push_str("|------|----------|--------|-------------|\n");
39            for t in &s.task_reports {
40                out.push_str(&format!(
41                    "| {} | {} | {:.1}% | {}/{} |\n",
42                    t.task_id,
43                    t.category.as_str(),
44                    t.metric.pass_at_k * 100.0,
45                    t.metric.passed_runs,
46                    t.metric.total_runs
47                ));
48            }
49            out.push('\n');
50        }
51        out
52    }
53}
54
55pub fn build_task_report(
56    task_id: &str,
57    _name: &str,
58    category: EvalCategory,
59    metric: EvalMetric,
60) -> TaskReport {
61    TaskReport {
62        task_id: task_id.into(),
63        category: category.label().into(),
64        metric,
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use crate::metric::EvalMetric;
72    use crate::task::EvalCategory;
73
74    #[test]
75    fn to_markdown_renders_tasks_and_aggregate() {
76        let report = EvalReport {
77            generated_at: "2026-01-01".into(),
78            suites: vec![SuiteReport {
79                suite_id: "s1".into(),
80                suite_name: "demo".into(),
81                task_reports: vec![TaskReport {
82                    task_id: "t1".into(),
83                    category: "Capability".into(),
84                    metric: EvalMetric {
85                        pass_at_k: 0.5,
86                        pass_all_k: 0.0,
87                        total_runs: 2,
88                        passed_runs: 1,
89                        task_id: "t1".into(),
90                    },
91                }],
92                aggregate: EvalMetric {
93                    pass_at_k: 0.5,
94                    pass_all_k: 0.0,
95                    total_runs: 2,
96                    passed_runs: 1,
97                    task_id: "aggregate".into(),
98                },
99                capability_metrics: EvalMetric {
100                    pass_at_k: 0.5,
101                    pass_all_k: 0.0,
102                    total_runs: 2,
103                    passed_runs: 1,
104                    task_id: "cap".into(),
105                },
106                regression_metrics: EvalMetric {
107                    pass_at_k: 0.0,
108                    pass_all_k: 0.0,
109                    total_runs: 0,
110                    passed_runs: 0,
111                    task_id: "reg".into(),
112                },
113            }],
114        };
115        let md = report.to_markdown();
116        assert!(md.contains("# Eval Report"));
117        assert!(md.contains("demo"));
118        assert!(md.contains("t1"));
119        assert!(md.contains("Capability"));
120    }
121
122    #[test]
123    fn build_task_report_maps_category() {
124        let tr = build_task_report(
125            "t1",
126            "name",
127            EvalCategory::Regression,
128            EvalMetric {
129                pass_at_k: 1.0,
130                pass_all_k: 1.0,
131                total_runs: 1,
132                passed_runs: 1,
133                task_id: "t1".into(),
134            },
135        );
136        assert_eq!(tr.task_id, "t1");
137        assert_eq!(tr.category, "Regression");
138    }
139}