Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/src/validation/analytics.rs

12.5 KiB, 64 runs

created by r1870400018:8616, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Metrics for validation: how often it runs, how often it passes, how long
2//! it takes, and which rules do the rejecting.
3//!
4//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
5//! Anthropic Claude
6
7use crate::{
8 validation::ValidationResult,
9};
10
11use oxedyne_fe2o3_core::prelude::*;
12
13use std::{
14 collections::HashMap,
15 time::{Duration, Instant},
16};
17
18/// # Examples
19///
20/// ```ignore
21/// use oxedyne_fe2o3_datime::validation::{ValidationAnalytics, CalClockValidator};
22///
23/// let mut analytics = ValidationAnalytics::new();
24/// let validator = CalClockValidator::new();
25///
26/// // Validate with analytics tracking
27/// let result = analytics.track_validation(|| {
28/// validator.validate_calclock(&some_calclock)
29/// });
30///
31/// // Get comprehensive metrics
32/// let metrics = analytics.get_metrics();
33/// println!("Success rate: {:.2}%", metrics.success_rate() * 100.0);
34/// ```
35#[derive(Debug, Clone)]
36pub struct ValidationAnalytics {
37 total_validations: u64,
38 successful_validations: u64,
39 failed_validations: u64,
40 total_validation_time: Duration,
41 error_frequency: HashMap<String, u64>,
42 rule_performance: HashMap<String, RulePerformance>,
43 recent_validations: Vec<ValidationRecord>,
44 max_history: usize,
45}
46
47impl ValidationAnalytics {
48 pub fn new() -> Self {
49 Self {
50 total_validations: 0,
51 successful_validations: 0,
52 failed_validations: 0,
53 total_validation_time: Duration::new(0, 0),
54 error_frequency: HashMap::new(),
55 rule_performance: HashMap::new(),
56 recent_validations: Vec::new(),
57 max_history: 1000,
58 }
59 }
60
61 pub fn with_history_size(max_history: usize) -> Self {
62 Self {
63 max_history,
64 ..Self::new()
65 }
66 }
67
68 pub fn track_validation<F, T>(&mut self, operation: F) -> T
69 where
70 F: FnOnce() -> T,
71 {
72 let start_time = Instant::now();
73 let result = operation();
74 let duration = start_time.elapsed();
75
76 self.total_validations += 1;
77 self.total_validation_time += duration;
78
79 // Add to recent history
80 let record = ValidationRecord {
81 timestamp: Instant::now(),
82 duration,
83 success: true, // This is a simplified version - would need actual result analysis
84 };
85
86 self.recent_validations.push(record);
87 if self.recent_validations.len() > self.max_history {
88 self.recent_validations.remove(0);
89 }
90
91 result
92 }
93
94 pub fn track_validation_result(&mut self, result: &ValidationResult, rule_name: &str, duration: Duration) {
95 self.total_validations += 1;
96 self.total_validation_time += duration;
97
98 match result {
99 Ok(_) => {
100 self.successful_validations += 1;
101 }
102 Err(errors) => {
103 self.failed_validations += 1;
104
105 // Track error frequency
106 for error in errors {
107 *self.error_frequency.entry(error.rule.clone()).or_insert(0) += 1;
108 }
109 }
110 }
111
112 // Update rule performance metrics
113 let perf = self.rule_performance.entry(rule_name.to_string()).or_insert_with(RulePerformance::new);
114 perf.add_measurement(duration, result.is_ok());
115
116 // Add to recent history
117 let record = ValidationRecord {
118 timestamp: Instant::now(),
119 duration,
120 success: result.is_ok(),
121 };
122
123 self.recent_validations.push(record);
124 if self.recent_validations.len() > self.max_history {
125 self.recent_validations.remove(0);
126 }
127 }
128
129 pub fn record_error(&mut self, rule_name: &str) {
130 *self.error_frequency.entry(rule_name.to_string()).or_insert(0) += 1;
131 }
132
133 pub fn get_metrics(&self) -> ValidationMetrics {
134 ValidationMetrics {
135 total_validations: self.total_validations,
136 successful_validations: self.successful_validations,
137 failed_validations: self.failed_validations,
138 total_validation_time: self.total_validation_time,
139 error_frequency: self.error_frequency.clone(),
140 rule_performance: self.rule_performance.clone(),
141 recent_trend: self.calculate_recent_trend(),
142 }
143 }
144
145 pub fn generate_report(&self) -> ValidationReport {
146 let metrics = self.get_metrics();
147 ValidationReport::new(metrics)
148 }
149
150 pub fn reset(&mut self) {
151 self.total_validations = 0;
152 self.successful_validations = 0;
153 self.failed_validations = 0;
154 self.total_validation_time = Duration::new(0, 0);
155 self.error_frequency.clear();
156 self.rule_performance.clear();
157 self.recent_validations.clear();
158 }
159
160 fn calculate_recent_trend(&self) -> ValidationTrend {
161 if self.recent_validations.len() < 10 {
162 return ValidationTrend::Insufficient;
163 }
164
165 let recent_count = std::cmp::min(100, self.recent_validations.len());
166 let recent = &self.recent_validations[self.recent_validations.len() - recent_count..];
167
168 let success_count = recent.iter().filter(|r| r.success).count();
169 let success_rate = success_count as f64 / recent.len() as f64;
170
171 if success_rate > 0.95 {
172 ValidationTrend::Excellent
173 } else if success_rate > 0.90 {
174 ValidationTrend::Good
175 } else if success_rate > 0.80 {
176 ValidationTrend::Moderate
177 } else {
178 ValidationTrend::Poor
179 }
180 }
181}
182
183impl Default for ValidationAnalytics {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189#[derive(Debug, Clone)]
190pub struct RulePerformance {
191 pub execution_count: u64,
192 pub total_time: Duration,
193 pub success_count: u64,
194 pub min_time: Option<Duration>,
195 pub max_time: Option<Duration>,
196}
197
198impl RulePerformance {
199 pub fn new() -> Self {
200 Self {
201 execution_count: 0,
202 total_time: Duration::new(0, 0),
203 success_count: 0,
204 min_time: None,
205 max_time: None,
206 }
207 }
208
209 pub fn add_measurement(&mut self, duration: Duration, success: bool) {
210 self.execution_count += 1;
211 self.total_time += duration;
212
213 if success {
214 self.success_count += 1;
215 }
216
217 match self.min_time {
218 None => self.min_time = Some(duration),
219 Some(min) if duration < min => self.min_time = Some(duration),
220 _ => {}
221 }
222
223 match self.max_time {
224 None => self.max_time = Some(duration),
225 Some(max) if duration > max => self.max_time = Some(duration),
226 _ => {}
227 }
228 }
229
230 pub fn average_time(&self) -> Duration {
231 if self.execution_count == 0 {
232 Duration::new(0, 0)
233 } else {
234 self.total_time / self.execution_count as u32
235 }
236 }
237
238 /// A fraction between zero and one, not a percentage.
239 pub fn success_rate(&self) -> f64 {
240 if self.execution_count == 0 {
241 0.0
242 } else {
243 self.success_count as f64 / self.execution_count as f64
244 }
245 }
246}
247
248impl Default for RulePerformance {
249 fn default() -> Self {
250 Self::new()
251 }
252}
253
254#[derive(Debug, Clone)]
255pub struct ValidationRecord {
256 pub timestamp: Instant,
257 pub duration: Duration,
258 pub success: bool,
259}
260
261#[derive(Debug, Clone)]
262pub struct ValidationMetrics {
263 pub total_validations: u64,
264 pub successful_validations: u64,
265 pub failed_validations: u64,
266 pub total_validation_time: Duration,
267 pub error_frequency: HashMap<String, u64>,
268 pub rule_performance: HashMap<String, RulePerformance>,
269 pub recent_trend: ValidationTrend,
270}
271
272impl ValidationMetrics {
273 /// A fraction between zero and one, not a percentage.
274 pub fn success_rate(&self) -> f64 {
275 if self.total_validations == 0 {
276 0.0
277 } else {
278 self.successful_validations as f64 / self.total_validations as f64
279 }
280 }
281
282 pub fn average_validation_time(&self) -> Duration {
283 if self.total_validations == 0 {
284 Duration::new(0, 0)
285 } else {
286 self.total_validation_time / self.total_validations as u32
287 }
288 }
289
290 pub fn most_frequent_error(&self) -> Option<(String, u64)> {
291 self.error_frequency
292 .iter()
293 .max_by_key(|(_, count)| *count)
294 .map(|(rule, count)| (rule.clone(), *count))
295 }
296
297 pub fn slowest_rule(&self) -> Option<(String, Duration)> {
298 self.rule_performance
299 .iter()
300 .max_by_key(|(_, perf)| perf.average_time())
301 .map(|(rule, perf)| (rule.clone(), perf.average_time()))
302 }
303}
304
305#[derive(Debug, Clone, PartialEq)]
306pub enum ValidationTrend {
307 // Banded by success rate over the recent history.
308 Insufficient, // too few records to say
309 Excellent, // above 95%
310 Good, // 90 to 95%
311 Moderate, // 80 to 90%
312 Poor, // below 80%
313}
314
315#[derive(Debug, Clone)]
316pub struct ValidationReport {
317 pub metrics: ValidationMetrics,
318 pub summary: String,
319 pub recommendations: Vec<String>,
320 pub error_analysis: Vec<String>,
321}
322
323impl ValidationReport {
324 pub fn new(metrics: ValidationMetrics) -> Self {
325 let mut report = Self {
326 metrics: metrics.clone(),
327 summary: String::new(),
328 recommendations: Vec::new(),
329 error_analysis: Vec::new(),
330 };
331
332 report.generate_summary();
333 report.generate_recommendations();
334 report.generate_error_analysis();
335
336 report
337 }
338
339 fn generate_summary(&mut self) {
340 let success_rate = self.metrics.success_rate() * 100.0;
341 let avg_time = self.metrics.average_validation_time();
342
343 self.summary = format!(
344 "Validation Summary: {:.1}% success rate across {} operations. \
345 Average validation time: {:?}. Trend: {:?}",
346 success_rate,
347 self.metrics.total_validations,
348 avg_time,
349 self.metrics.recent_trend
350 );
351 }
352
353 fn generate_recommendations(&mut self) {
354 // Performance recommendations
355 if let Some((rule, duration)) = self.metrics.slowest_rule() {
356 if duration.as_millis() > 10 {
357 self.recommendations.push(format!(
358 "Consider optimizing '{}' rule - average execution time: {:?}",
359 rule, duration
360 ));
361 }
362 }
363
364 // Success rate recommendations
365 if self.metrics.success_rate() < 0.95 {
366 self.recommendations.push(
367 "Success rate below 95% - consider reviewing validation rules".to_string()
368 );
369 }
370
371 // Trend recommendations
372 match self.metrics.recent_trend {
373 ValidationTrend::Poor => {
374 self.recommendations.push(
375 "Poor recent performance - immediate investigation recommended".to_string()
376 );
377 }
378 ValidationTrend::Moderate => {
379 self.recommendations.push(
380 "Moderate performance - consider rule adjustments".to_string()
381 );
382 }
383 _ => {}
384 }
385 }
386
387 fn generate_error_analysis(&mut self) {
388 if let Some((rule, count)) = self.metrics.most_frequent_error() {
389 self.error_analysis.push(format!(
390 "Most frequent error: '{}' with {} occurrences", rule, count
391 ));
392 }
393
394 let total_errors = self.metrics.failed_validations;
395 if total_errors > 0 {
396 self.error_analysis.push(format!(
397 "Total errors: {} ({:.1}% of all validations)",
398 total_errors,
399 (total_errors as f64 / self.metrics.total_validations as f64) * 100.0
400 ));
401 }
402 }
403
404 pub fn format(&self) -> String {
405 let mut output = String::new();
406
407 output.push_str("=== Validation Report ===\n\n");
408 output.push_str(&format!("{}\n\n", self.summary));
409
410 if !self.recommendations.is_empty() {
411 output.push_str("Recommendations:\n");
412 for rec in &self.recommendations {
413 output.push_str(&format!("- {}\n", rec));
414 }
415 output.push('\n');
416 }
417
418 if !self.error_analysis.is_empty() {
419 output.push_str("Error Analysis:\n");
420 for analysis in &self.error_analysis {
421 output.push_str(&format!("- {}\n", analysis));
422 }
423 }
424
425 output
426 }
427}