1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
20pub enum EmlEvent {
21 Trained {
23 model_name: String,
24 samples_used: usize,
25 mse_before: f64,
26 mse_after: f64,
27 converged: bool,
28 param_count: usize,
29 },
30 Prediction {
32 model_name: String,
33 inputs_hash: String,
35 output: Vec<f64>,
36 },
37 Drift {
39 model_name: String,
40 predicted: f64,
41 actual: f64,
42 drift_pct: f64,
43 },
44 Saved {
46 model_name: String,
47 path: String,
48 param_count: usize,
49 },
50 Loaded {
52 model_name: String,
53 path: String,
54 trained: bool,
55 samples: usize,
56 },
57 Reset {
59 model_name: String,
60 reason: String,
61 },
62}
63
64impl EmlEvent {
65 pub fn event_type(&self) -> &'static str {
69 match self {
70 EmlEvent::Trained { .. } => "eml.trained",
71 EmlEvent::Prediction { .. } => "eml.prediction",
72 EmlEvent::Drift { .. } => "eml.drift",
73 EmlEvent::Saved { .. } => "eml.saved",
74 EmlEvent::Loaded { .. } => "eml.loaded",
75 EmlEvent::Reset { .. } => "eml.reset",
76 }
77 }
78
79 pub fn model_name(&self) -> &str {
81 match self {
82 EmlEvent::Trained { model_name, .. }
83 | EmlEvent::Prediction { model_name, .. }
84 | EmlEvent::Drift { model_name, .. }
85 | EmlEvent::Saved { model_name, .. }
86 | EmlEvent::Loaded { model_name, .. }
87 | EmlEvent::Reset { model_name, .. } => model_name,
88 }
89 }
90}
91
92#[derive(Debug, Clone, Default)]
101pub struct EmlEventLog {
102 events: Vec<EmlEvent>,
103}
104
105impl EmlEventLog {
106 pub fn new() -> Self {
108 Self {
109 events: Vec::new(),
110 }
111 }
112
113 pub fn push(&mut self, event: EmlEvent) {
115 self.events.push(event);
116 }
117
118 pub fn drain(&mut self) -> Vec<EmlEvent> {
120 std::mem::take(&mut self.events)
121 }
122
123 pub fn len(&self) -> usize {
125 self.events.len()
126 }
127
128 pub fn is_empty(&self) -> bool {
130 self.events.is_empty()
131 }
132}
133
134#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn event_type_strings() {
144 let e = EmlEvent::Trained {
145 model_name: "test".into(),
146 samples_used: 100,
147 mse_before: 1.0,
148 mse_after: 0.01,
149 converged: true,
150 param_count: 50,
151 };
152 assert_eq!(e.event_type(), "eml.trained");
153
154 let e = EmlEvent::Drift {
155 model_name: "test".into(),
156 predicted: 1.0,
157 actual: 1.1,
158 drift_pct: 10.0,
159 };
160 assert_eq!(e.event_type(), "eml.drift");
161 }
162
163 #[test]
164 fn event_log_drain() {
165 let mut log = EmlEventLog::new();
166 assert!(log.is_empty());
167
168 log.push(EmlEvent::Reset {
169 model_name: "test".into(),
170 reason: "manual".into(),
171 });
172 assert_eq!(log.len(), 1);
173
174 let drained = log.drain();
175 assert_eq!(drained.len(), 1);
176 assert!(log.is_empty());
177 }
178
179 #[test]
180 fn model_name_accessor() {
181 let e = EmlEvent::Saved {
182 model_name: "coherence".into(),
183 path: "/tmp/test".into(),
184 param_count: 50,
185 };
186 assert_eq!(e.model_name(), "coherence");
187 }
188}