1use crate::error::{Result, Web4Error};
24use crate::lct::{EntityType, Lct};
25use serde::{Deserialize, Serialize};
26use uuid::Uuid;
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
30pub struct Coherence {
31 pub continuity: f64,
34
35 pub stability: f64,
38
39 pub phi: f64,
42
43 pub reachability: f64,
46}
47
48impl Default for Coherence {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl Coherence {
55 pub fn new() -> Self {
57 Self {
58 continuity: 0.5,
59 stability: 0.5,
60 phi: 0.5,
61 reachability: 0.5,
62 }
63 }
64
65 pub fn with_values(continuity: f64, stability: f64, phi: f64, reachability: f64) -> Result<Self> {
67 for (name, value) in [
68 ("continuity", continuity),
69 ("stability", stability),
70 ("phi", phi),
71 ("reachability", reachability),
72 ] {
73 if !(0.0..=1.0).contains(&value) {
74 return Err(Web4Error::InvalidInput(format!(
75 "{} must be in range [0.0, 1.0]",
76 name
77 )));
78 }
79 }
80 Ok(Self {
81 continuity,
82 stability,
83 phi,
84 reachability,
85 })
86 }
87
88 pub fn total(&self) -> f64 {
90 self.continuity * self.stability * self.phi * self.reachability
91 }
92
93 pub fn meets_threshold(&self, threshold: f64) -> bool {
95 self.total() >= threshold
96 }
97
98 pub fn limiting_factor(&self) -> (&'static str, f64) {
100 let factors = [
101 ("continuity", self.continuity),
102 ("stability", self.stability),
103 ("phi", self.phi),
104 ("reachability", self.reachability),
105 ];
106 *factors.iter().min_by(|a, b| a.1.partial_cmp(&b.1).unwrap()).unwrap()
107 }
108}
109
110pub struct CoherenceCalculator {
112 continuity_window: u64,
114
115 min_interactions: u64,
117
118 network_depth: u32,
120}
121
122impl Default for CoherenceCalculator {
123 fn default() -> Self {
124 Self {
125 continuity_window: 86400 * 30, min_interactions: 10,
127 network_depth: 3,
128 }
129 }
130}
131
132impl CoherenceCalculator {
133 pub fn new(continuity_window: u64, min_interactions: u64, network_depth: u32) -> Self {
135 Self {
136 continuity_window,
137 min_interactions,
138 network_depth,
139 }
140 }
141
142 pub fn calculate_continuity(&self, activity_timestamps: &[i64]) -> f64 {
147 if activity_timestamps.is_empty() {
148 return 0.0;
149 }
150 if activity_timestamps.len() == 1 {
151 return 0.1;
152 }
153
154 let mut timestamps: Vec<i64> = activity_timestamps.to_vec();
155 timestamps.sort_unstable();
156
157 let gaps: Vec<i64> = timestamps.windows(2).map(|w| w[1] - w[0]).collect();
159 let avg_gap = gaps.iter().sum::<i64>() as f64 / gaps.len() as f64;
160 let expected_gap = self.continuity_window as f64 / activity_timestamps.len() as f64;
161
162 let gap_consistency = 1.0 / (1.0 + (avg_gap / expected_gap - 1.0).abs());
164
165 let total_span = (timestamps.last().unwrap() - timestamps.first().unwrap()) as f64;
167 let coverage = (total_span / self.continuity_window as f64).min(1.0);
168
169 (gap_consistency * 0.7 + coverage * 0.3).min(1.0)
170 }
171
172 pub fn calculate_stability(&self, interaction_scores: &[f64]) -> f64 {
177 if interaction_scores.len() < self.min_interactions as usize {
178 return 0.3;
180 }
181
182 let mean: f64 = interaction_scores.iter().sum::<f64>() / interaction_scores.len() as f64;
183 let variance: f64 = interaction_scores
184 .iter()
185 .map(|x| (x - mean).powi(2))
186 .sum::<f64>()
187 / interaction_scores.len() as f64;
188 let std_dev = variance.sqrt();
189
190 1.0 / (1.0 + (std_dev * 5.0).exp())
193 }
194
195 pub fn calculate_phi(&self, context_sensitivity: f64, cross_reference_density: f64) -> f64 {
205 ((context_sensitivity + cross_reference_density) / 2.0).min(1.0).max(0.0)
208 }
209
210 pub fn calculate_reachability(
215 &self,
216 direct_connections: u32,
217 indirect_connections: u32,
218 max_connections: u32,
219 ) -> f64 {
220 if max_connections == 0 {
221 return 0.0;
222 }
223
224 let direct_weight = 0.7;
226 let indirect_weight = 0.3;
227
228 let direct_ratio = (direct_connections as f64 / max_connections as f64).min(1.0);
229 let indirect_ratio = (indirect_connections as f64 / (max_connections * self.network_depth) as f64).min(1.0);
230
231 direct_weight * direct_ratio + indirect_weight * indirect_ratio
232 }
233
234 pub fn calculate(&self, params: &CoherenceParams) -> Coherence {
236 let continuity = self.calculate_continuity(¶ms.activity_timestamps);
237 let stability = self.calculate_stability(¶ms.interaction_scores);
238 let phi = self.calculate_phi(params.context_sensitivity, params.cross_reference_density);
239 let reachability = self.calculate_reachability(
240 params.direct_connections,
241 params.indirect_connections,
242 params.max_connections,
243 );
244
245 Coherence {
246 continuity,
247 stability,
248 phi,
249 reachability,
250 }
251 }
252}
253
254#[derive(Clone, Debug, Default)]
256pub struct CoherenceParams {
257 pub activity_timestamps: Vec<i64>,
259
260 pub interaction_scores: Vec<f64>,
262
263 pub context_sensitivity: f64,
265
266 pub cross_reference_density: f64,
268
269 pub direct_connections: u32,
271
272 pub indirect_connections: u32,
274
275 pub max_connections: u32,
277}
278
279pub fn coherence_threshold_for_entity(entity_type: &EntityType) -> f64 {
281 match entity_type {
282 EntityType::Human => 0.5, EntityType::AiEmbodied => 0.6, EntityType::AiSoftware => 0.7, EntityType::Organization => 0.5,
286 EntityType::Role => 0.5,
287 EntityType::Task => 0.3,
288 EntityType::Resource => 0.3,
289 EntityType::Hybrid => 0.6,
290 }
291}
292
293pub fn check_coherence(lct: &Lct, coherence: &Coherence) -> Result<()> {
295 let threshold = coherence_threshold_for_entity(&lct.entity_type);
296
297 let effective_threshold = threshold.max(1.0 - lct.trust_ceiling());
299
300 if coherence.total() < effective_threshold {
301 return Err(Web4Error::CoherenceBelowThreshold {
302 score: coherence.total(),
303 threshold: effective_threshold,
304 });
305 }
306
307 Ok(())
308}
309
310#[derive(Clone, Debug, Serialize, Deserialize)]
312pub struct CoherenceEvent {
313 pub entity_id: Uuid,
315
316 pub coherence: Coherence,
318
319 pub timestamp: chrono::DateTime<chrono::Utc>,
321
322 pub context: Option<String>,
324}
325
326impl CoherenceEvent {
327 pub fn new(entity_id: Uuid, coherence: Coherence) -> Self {
329 Self {
330 entity_id,
331 coherence,
332 timestamp: chrono::Utc::now(),
333 context: None,
334 }
335 }
336
337 pub fn with_context(entity_id: Uuid, coherence: Coherence, context: impl Into<String>) -> Self {
339 Self {
340 entity_id,
341 coherence,
342 timestamp: chrono::Utc::now(),
343 context: Some(context.into()),
344 }
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn test_coherence_total() {
354 let c = Coherence::with_values(0.8, 0.8, 0.8, 0.8).unwrap();
355 let total = c.total();
356 assert!((total - 0.4096).abs() < 0.001); }
358
359 #[test]
360 fn test_zero_factor_zeros_total() {
361 let c = Coherence::with_values(0.9, 0.9, 0.0, 0.9).unwrap();
362 assert_eq!(c.total(), 0.0);
363 }
364
365 #[test]
366 fn test_limiting_factor() {
367 let c = Coherence::with_values(0.9, 0.5, 0.8, 0.7).unwrap();
368 let (name, value) = c.limiting_factor();
369 assert_eq!(name, "stability");
370 assert_eq!(value, 0.5);
371 }
372
373 #[test]
374 fn test_continuity_calculation() {
375 let calc = CoherenceCalculator::default();
376
377 let regular: Vec<i64> = (0..30).map(|i| i * 86400).collect();
379 let continuity = calc.calculate_continuity(®ular);
380 assert!(continuity > 0.5);
381
382 assert_eq!(calc.calculate_continuity(&[]), 0.0);
384 }
385
386 #[test]
387 fn test_stability_calculation() {
388 let calc = CoherenceCalculator::default();
389
390 let consistent: Vec<f64> = vec![0.8, 0.82, 0.79, 0.81, 0.8, 0.78, 0.81, 0.79, 0.8, 0.82];
392 let stability = calc.calculate_stability(&consistent);
393 assert!(stability > 0.4); let erratic: Vec<f64> = vec![0.1, 0.9, 0.2, 0.8, 0.3, 0.7, 0.15, 0.85, 0.25, 0.75];
397 let stability_erratic = calc.calculate_stability(&erratic);
398 assert!(stability_erratic < stability); }
400
401 #[test]
402 fn test_entity_thresholds() {
403 assert_eq!(coherence_threshold_for_entity(&EntityType::Human), 0.5);
404 assert_eq!(coherence_threshold_for_entity(&EntityType::AiSoftware), 0.7);
405 assert_eq!(coherence_threshold_for_entity(&EntityType::Task), 0.3);
406 }
407
408 #[test]
409 fn test_coherence_check() {
410 let (lct, _) = Lct::new(EntityType::Human, None);
411
412 let high = Coherence::with_values(0.9, 0.9, 0.9, 0.9).unwrap();
414 assert!(check_coherence(&lct, &high).is_ok());
415
416 let low = Coherence::with_values(0.5, 0.5, 0.5, 0.5).unwrap();
418 assert!(check_coherence(&lct, &low).is_err());
419 }
420}