synth_ai_core/data/
judgements.rs1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct CriterionScoreData {
12 pub score: f64,
14 #[serde(default)]
16 pub reason: Option<String>,
17 #[serde(default = "default_weight")]
19 pub weight: f64,
20 #[serde(default)]
22 pub normalized_score: Option<f64>,
23 #[serde(default)]
25 pub passed: Option<bool>,
26}
27
28fn default_weight() -> f64 {
29 1.0
30}
31
32impl CriterionScoreData {
33 pub fn new(score: f64) -> Self {
35 Self {
36 score,
37 reason: None,
38 weight: 1.0,
39 normalized_score: None,
40 passed: None,
41 }
42 }
43
44 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
46 self.reason = Some(reason.into());
47 self
48 }
49
50 pub fn with_weight(mut self, weight: f64) -> Self {
52 self.weight = weight;
53 self
54 }
55
56 pub fn with_passed(mut self, passed: bool) -> Self {
58 self.passed = Some(passed);
59 self
60 }
61
62 pub fn weighted_score(&self) -> f64 {
64 self.score * self.weight
65 }
66}
67
68impl Default for CriterionScoreData {
69 fn default() -> Self {
70 Self::new(0.0)
71 }
72}
73
74#[derive(Debug, Clone, Default, Serialize, Deserialize)]
76pub struct RubricAssignment {
77 #[serde(default)]
79 pub criterion_scores: HashMap<String, CriterionScoreData>,
80 #[serde(default)]
82 pub total: f64,
83 #[serde(default)]
85 pub rubric_ref: Option<String>,
86 #[serde(default)]
88 pub summary: Option<String>,
89 #[serde(default)]
91 pub all_required_passed: Option<bool>,
92 #[serde(default)]
94 pub normalized_total: Option<f64>,
95}
96
97impl RubricAssignment {
98 pub fn new() -> Self {
100 Self::default()
101 }
102
103 pub fn with_score(mut self, criterion_id: impl Into<String>, score: CriterionScoreData) -> Self {
105 self.criterion_scores.insert(criterion_id.into(), score);
106 self
107 }
108
109 pub fn with_total(mut self, total: f64) -> Self {
111 self.total = total;
112 self
113 }
114
115 pub fn with_rubric_ref(mut self, rubric_ref: impl Into<String>) -> Self {
117 self.rubric_ref = Some(rubric_ref.into());
118 self
119 }
120
121 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
123 self.summary = Some(summary.into());
124 self
125 }
126
127 pub fn calculate_weighted_total(&mut self) {
129 let total_weight: f64 = self.criterion_scores.values().map(|s| s.weight).sum();
130 if total_weight > 0.0 {
131 let weighted_sum: f64 = self.criterion_scores.values().map(|s| s.weighted_score()).sum();
132 self.total = weighted_sum / total_weight;
133 }
134 }
135
136 pub fn get_score(&self, criterion_id: &str) -> Option<f64> {
138 self.criterion_scores.get(criterion_id).map(|s| s.score)
139 }
140}
141
142#[derive(Debug, Clone, Default, Serialize, Deserialize)]
144pub struct Judgement {
145 #[serde(default)]
147 pub rubric_assignment: Option<RubricAssignment>,
148 #[serde(default)]
150 pub annotation: HashMap<String, Value>,
151 #[serde(default)]
153 pub passed: Option<bool>,
154 #[serde(default)]
156 pub confidence: Option<f64>,
157 #[serde(default)]
159 pub source: Option<String>,
160 #[serde(default)]
162 pub judged_at: Option<String>,
163}
164
165impl Judgement {
166 pub fn new() -> Self {
168 Self::default()
169 }
170
171 pub fn with_rubric_assignment(mut self, assignment: RubricAssignment) -> Self {
173 self.rubric_assignment = Some(assignment);
174 self
175 }
176
177 pub fn with_annotation(mut self, key: impl Into<String>, value: Value) -> Self {
179 self.annotation.insert(key.into(), value);
180 self
181 }
182
183 pub fn with_passed(mut self, passed: bool) -> Self {
185 self.passed = Some(passed);
186 self
187 }
188
189 pub fn with_confidence(mut self, confidence: f64) -> Self {
191 self.confidence = Some(confidence.clamp(0.0, 1.0));
192 self
193 }
194
195 pub fn with_source(mut self, source: impl Into<String>) -> Self {
197 self.source = Some(source.into());
198 self
199 }
200
201 pub fn total_score(&self) -> Option<f64> {
203 self.rubric_assignment.as_ref().map(|a| a.total)
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_criterion_score() {
213 let score = CriterionScoreData::new(8.5)
214 .with_reason("Good explanation")
215 .with_weight(2.0);
216
217 assert_eq!(score.score, 8.5);
218 assert_eq!(score.weighted_score(), 17.0);
219 }
220
221 #[test]
222 fn test_rubric_assignment() {
223 let mut assignment = RubricAssignment::new()
224 .with_score("clarity", CriterionScoreData::new(9.0).with_weight(1.0))
225 .with_score("accuracy", CriterionScoreData::new(7.0).with_weight(2.0))
226 .with_rubric_ref("eval_v1");
227
228 assignment.calculate_weighted_total();
229
230 assert!((assignment.total - 7.666).abs() < 0.01);
232 }
233
234 #[test]
235 fn test_judgement() {
236 let assignment = RubricAssignment::new()
237 .with_total(8.5)
238 .with_summary("Good overall performance");
239
240 let judgement = Judgement::new()
241 .with_rubric_assignment(assignment)
242 .with_passed(true)
243 .with_confidence(0.95)
244 .with_source("verifier");
245
246 assert_eq!(judgement.total_score(), Some(8.5));
247 assert_eq!(judgement.passed, Some(true));
248 assert_eq!(judgement.confidence, Some(0.95));
249 }
250
251 #[test]
252 fn test_serde() {
253 let judgement = Judgement::new()
254 .with_passed(true)
255 .with_annotation("note", serde_json::json!("test"));
256
257 let json = serde_json::to_string(&judgement).unwrap();
258 let parsed: Judgement = serde_json::from_str(&json).unwrap();
259
260 assert_eq!(parsed.passed, Some(true));
261 assert_eq!(parsed.annotation.get("note"), Some(&serde_json::json!("test")));
262 }
263}