entrenar/integrity/behavioral/
metrics.rs1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9use super::assessment::IntegrityAssessment;
10use super::counts::ViolationCounts;
11use super::violation::{MetamorphicRelationType, MetamorphicViolation};
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct BehavioralIntegrity {
19 pub equivalence_score: f64,
22
23 pub syscall_match: f64,
26
27 pub timing_variance: f64,
30
31 pub semantic_equiv: f64,
34
35 pub violations: Vec<MetamorphicViolation>,
37
38 pub timestamp: DateTime<Utc>,
40
41 pub test_count: u32,
43
44 pub model_id: String,
46}
47
48impl BehavioralIntegrity {
49 pub fn new(
51 equivalence_score: f64,
52 syscall_match: f64,
53 timing_variance: f64,
54 semantic_equiv: f64,
55 model_id: impl Into<String>,
56 ) -> Self {
57 Self {
58 equivalence_score: equivalence_score.clamp(0.0, 1.0),
59 syscall_match: syscall_match.clamp(0.0, 1.0),
60 timing_variance: timing_variance.clamp(0.0, 1.0),
61 semantic_equiv: semantic_equiv.clamp(0.0, 1.0),
62 violations: Vec::new(),
63 timestamp: Utc::now(),
64 test_count: 0,
65 model_id: model_id.into(),
66 }
67 }
68
69 pub fn perfect(model_id: impl Into<String>) -> Self {
71 Self::new(1.0, 1.0, 0.0, 1.0, model_id)
72 }
73
74 pub fn add_violation(&mut self, violation: MetamorphicViolation) {
76 self.violations.push(violation);
77 }
78
79 pub fn with_test_count(mut self, count: u32) -> Self {
81 self.test_count = count;
82 self
83 }
84
85 pub fn composite_score(&self) -> f64 {
89 const W_EQUIV: f64 = 0.3;
91 const W_SYSCALL: f64 = 0.2;
92 const W_TIMING: f64 = 0.2;
93 const W_SEMANTIC: f64 = 0.3;
94
95 let timing_score = 1.0 - self.timing_variance; W_EQUIV * self.equivalence_score
98 + W_SYSCALL * self.syscall_match
99 + W_TIMING * timing_score
100 + W_SEMANTIC * self.semantic_equiv
101 }
102
103 pub fn passes_gate(&self, threshold: f64) -> bool {
110 self.composite_score() >= threshold
111 && !self.has_critical_violations()
112 && self.timing_variance < 0.2
113 }
114
115 pub fn has_critical_violations(&self) -> bool {
117 self.violations.iter().any(MetamorphicViolation::is_critical)
118 }
119
120 pub fn violation_counts(&self) -> ViolationCounts {
122 let critical = self.violations.iter().filter(|v| v.is_critical()).count() as u32;
123 let warnings =
124 self.violations.iter().filter(|v| v.is_warning() && !v.is_critical()).count() as u32;
125 let minor = self.violations.iter().filter(|v| !v.is_warning()).count() as u32;
126
127 ViolationCounts { critical, warnings, minor, total: self.violations.len() as u32 }
128 }
129
130 pub fn violations_by_type(
132 &self,
133 ) -> std::collections::HashMap<MetamorphicRelationType, Vec<&MetamorphicViolation>> {
134 let mut map = std::collections::HashMap::new();
135 for v in &self.violations {
136 map.entry(v.relation_type).or_insert_with(Vec::new).push(v);
137 }
138 map
139 }
140
141 pub fn most_severe_violation(&self) -> Option<&MetamorphicViolation> {
143 self.violations
144 .iter()
145 .max_by(|a, b| a.severity.partial_cmp(&b.severity).unwrap_or(std::cmp::Ordering::Equal))
146 }
147
148 pub fn assessment(&self) -> IntegrityAssessment {
150 let score = self.composite_score();
151 let counts = self.violation_counts();
152
153 if counts.critical > 0 {
154 IntegrityAssessment::Critical
155 } else if score < 0.5 {
156 IntegrityAssessment::Poor
157 } else if score < 0.7 {
158 IntegrityAssessment::Fair
159 } else if score < 0.9 {
160 IntegrityAssessment::Good
161 } else {
162 IntegrityAssessment::Excellent
163 }
164 }
165
166 pub fn summary(&self) -> String {
168 let counts = self.violation_counts();
169 format!(
170 "Model: {}\n\
171 Composite Score: {:.1}%\n\
172 Assessment: {}\n\
173 Violations: {} critical, {} warnings, {} minor\n\
174 Tests Run: {}\n\
175 Gate Status: {}",
176 self.model_id,
177 self.composite_score() * 100.0,
178 self.assessment(),
179 counts.critical,
180 counts.warnings,
181 counts.minor,
182 self.test_count,
183 if self.passes_gate(0.9) { "PASS" } else { "FAIL" }
184 )
185 }
186}