1use std::collections::{HashMap, HashSet, VecDeque};
19use std::sync::{Arc, RwLock};
20use std::time::{SystemTime, UNIX_EPOCH};
21#[derive(Debug, Clone, PartialEq, Hash)]
25pub enum PatternType {
26 Sequence,
28 Temporal,
30 Structural,
32 Cluster,
34 CrossDomain,
36 Unknown,
38}
39
40#[derive(Debug, Clone)]
42pub struct PatternSignificance {
43 pub confidence: f64,
45 pub frequency: u64,
47 pub last_matched_ms: u64,
49 pub avg_similarity: f64,
51 pub learning_rate: f64,
53}
54
55impl Default for PatternSignificance {
56 fn default() -> Self {
57 Self {
58 confidence: 0.1,
59 frequency: 0,
60 last_matched_ms: 0,
61 avg_similarity: 0.0,
62 learning_rate: 0.3,
63 }
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct RecognizedPattern {
70 pub id: u64,
71 pub name: String,
72 pub pattern_type: PatternType,
73 pub template: PatternTemplate,
75 pub significance: PatternSignificance,
76 pub tags: Vec<String>,
77 pub domain: String,
79 pub metadata: HashMap<String, String>,
80}
81
82#[derive(Debug, Clone)]
84pub enum PatternTemplate {
85 Sequence(Vec<String>),
87 Temporal(Vec<TemporalSlice>),
89 Structural(Vec<(String, String, f64)>),
91 Cluster {
93 center: (f64, f64, f64),
94 radius: f64,
95 elements: Vec<String>,
96 },
97 Composite(Vec<u64>),
99}
100
101#[derive(Debug, Clone)]
103pub struct TemporalSlice {
104 pub hour: u8,
105 pub day_of_week: u8,
106 pub frequency: f64,
107 pub avg_intensity: f64,
108 pub activity_ids: Vec<String>,
109}
110
111#[derive(Debug, Clone)]
113pub struct PatternMatch {
114 pub pattern_id: u64,
115 pub target_description: String,
116 pub similarity: f64,
117 pub timestamp_ms: u64,
118 pub matched_elements: Vec<String>,
119 pub details: String,
120}
121
122#[derive(Debug, Clone)]
124pub struct RecognitionConfig {
125 pub min_similarity: f64,
127 pub min_confidence: f64,
129 pub confirmation_threshold: u64,
131 pub max_patterns: usize,
133 pub cluster_radius: f64,
135 pub sequence_window: usize,
137 pub learning_rate: f64,
139}
140
141impl Default for RecognitionConfig {
142 fn default() -> Self {
143 Self {
144 min_similarity: 0.6,
145 min_confidence: 0.3,
146 confirmation_threshold: 3,
147 max_patterns: 1000,
148 cluster_radius: 0.15,
149 sequence_window: 10,
150 learning_rate: 0.3,
151 }
152 }
153}
154pub struct PatternRecognizer {
158 patterns: Arc<RwLock<Vec<RecognizedPattern>>>,
160 match_history: Arc<RwLock<VecDeque<PatternMatch>>>,
162 config: Arc<RwLock<RecognitionConfig>>,
164 next_id: Arc<RwLock<u64>>,
166 domain_index: Arc<RwLock<HashMap<String, Vec<u64>>>>,
168}
169
170impl Default for PatternRecognizer {
171 fn default() -> Self {
172 Self::new()
173 }
174}
175
176impl PatternRecognizer {
177 pub fn new() -> Self {
178 Self {
179 patterns: Arc::new(RwLock::new(Vec::new())),
180 match_history: Arc::new(RwLock::new(VecDeque::with_capacity(1000))),
181 config: Arc::new(RwLock::new(RecognitionConfig::default())),
182 next_id: Arc::new(RwLock::new(1)),
183 domain_index: Arc::new(RwLock::new(HashMap::new())),
184 }
185 }
186
187 pub fn with_config(config: RecognitionConfig) -> Self {
188 Self {
189 config: Arc::new(RwLock::new(config)),
190 ..Self::new()
191 }
192 }
193
194 fn next_id(&self) -> u64 {
195 let mut id = crate::sync_guard::write_lock(&self.next_id);
196 let n = *id;
197 *id += 1;
198 n
199 }
200
201 fn now_ms(&self) -> u64 {
202 SystemTime::now()
203 .duration_since(UNIX_EPOCH)
204 .unwrap_or_default()
205 .as_millis() as u64
206 }
207
208 pub fn register_pattern(
212 &self,
213 name: &str,
214 ptype: PatternType,
215 template: PatternTemplate,
216 domain: &str,
217 tags: Vec<String>,
218 ) -> u64 {
219 let id = self.next_id();
220 let pattern = RecognizedPattern {
221 id,
222 name: name.to_string(),
223 pattern_type: ptype,
224 template,
225 significance: PatternSignificance::default(),
226 tags,
227 domain: domain.to_string(),
228 metadata: HashMap::new(),
229 };
230
231 crate::sync_guard::write_lock(&self.patterns).push(pattern);
232 crate::sync_guard::write_lock(&self.domain_index)
233 .entry(domain.to_string())
234 .or_default()
235 .push(id);
236
237 id
238 }
239
240 pub fn forget_pattern(&self, id: u64) -> bool {
242 let mut patterns = crate::sync_guard::write_lock(&self.patterns);
243 if let Some(pos) = patterns.iter().position(|p| p.id == id) {
244 let p = patterns.remove(pos);
245 if let Some(ids) = crate::sync_guard::write_lock(&self.domain_index).get_mut(&p.domain)
246 {
247 ids.retain(|&i| i != id);
248 }
249 true
250 } else {
251 false
252 }
253 }
254
255 pub fn list_patterns(
257 &self,
258 ptype: Option<PatternType>,
259 domain: Option<&str>,
260 ) -> Vec<RecognizedPattern> {
261 crate::sync_guard::read_lock(&self.patterns)
262 .iter()
263 .filter(|p| {
264 let type_match = ptype.as_ref().is_none_or(|t| p.pattern_type == *t);
265 let domain_match = domain.is_none_or(|d| p.domain == d);
266 type_match && domain_match
267 })
268 .cloned()
269 .collect()
270 }
271
272 pub fn find_pattern(&self, name: &str) -> Option<RecognizedPattern> {
274 crate::sync_guard::read_lock(&self.patterns)
275 .iter()
276 .find(|p| p.name == name)
277 .cloned()
278 }
279
280 pub fn get_pattern(&self, id: u64) -> Option<RecognizedPattern> {
282 crate::sync_guard::read_lock(&self.patterns)
283 .iter()
284 .find(|p| p.id == id)
285 .cloned()
286 }
287
288 pub fn find_sequences(&self, elements: &[String], domain: &str) -> Vec<RecognizedPattern> {
294 let config = crate::sync_guard::read_lock(&self.config).clone();
295 let window = config.sequence_window;
296 let min_sim = config.min_similarity;
297 let mut patterns = Vec::new();
298
299 if elements.len() < 2 {
300 return patterns;
301 }
302
303 let mut seq_counts: HashMap<Vec<String>, u64> = HashMap::new();
305
306 for len in 2..=window.min(elements.len()).min(5) {
307 for i in 0..=elements.len().saturating_sub(len) {
308 let subseq: Vec<String> = elements[i..i + len].to_vec();
309 *seq_counts.entry(subseq).or_insert(0) += 1;
310 }
311 }
312
313 let total = elements.len() as f64;
315 for (seq, count) in &seq_counts {
316 let freq = *count as f64 / total;
317 if freq >= min_sim && seq.len() >= 2 {
318 let confidence = (freq * (1.0 - 1.0 / (seq.len() as f64))).min(1.0);
319
320 let pattern = RecognizedPattern {
321 id: self.next_id(),
322 name: format!("seq_{}_{}", domain, seq.first().unwrap_or(&"?".to_string())),
323 pattern_type: PatternType::Sequence,
324 template: PatternTemplate::Sequence(seq.clone()),
325 significance: PatternSignificance {
326 confidence,
327 frequency: *count,
328 last_matched_ms: self.now_ms(),
329 avg_similarity: freq,
330 learning_rate: config.learning_rate,
331 },
332 tags: vec!["auto_detected".to_string(), format!("len_{}", seq.len())],
333 domain: domain.to_string(),
334 metadata: {
335 let mut m = HashMap::new();
336 m.insert("frequency".to_string(), format!("{:.2}", freq));
337 m.insert("sample_count".to_string(), count.to_string());
338 m
339 },
340 };
341
342 let exists = crate::sync_guard::read_lock(&self.patterns)
344 .iter()
345 .any(|p| {
346 if let PatternTemplate::Sequence(ref existing) = p.template {
347 existing == seq
348 } else {
349 false
350 }
351 });
352
353 if !exists {
354 let id = pattern.id;
355 crate::sync_guard::write_lock(&self.patterns).push(pattern.clone());
356 crate::sync_guard::write_lock(&self.domain_index)
357 .entry(domain.to_string())
358 .or_default()
359 .push(id);
360 }
361
362 patterns.push(pattern);
363 }
364 }
365
366 patterns.sort_by(|a, b| {
367 b.significance
368 .confidence
369 .partial_cmp(&a.significance.confidence)
370 .unwrap()
371 });
372 patterns
373 }
374
375 pub fn match_sequence(&self, sequence: &[String]) -> Vec<PatternMatch> {
377 let config = crate::sync_guard::read_lock(&self.config).clone();
378 let mut matches = Vec::new();
379 let now = self.now_ms();
380
381 for pattern in crate::sync_guard::read_lock(&self.patterns).iter() {
382 if let PatternTemplate::Sequence(ref pattern_seq) = pattern.template {
383 if pattern_seq.len() > sequence.len() {
385 continue;
386 }
387
388 for i in 0..=sequence.len().saturating_sub(pattern_seq.len()) {
389 let window = &sequence[i..i + pattern_seq.len()];
390 let sim = self.sequence_similarity(window, pattern_seq);
391
392 if sim >= config.min_similarity {
393 matches.push(PatternMatch {
394 pattern_id: pattern.id,
395 target_description: format!(
396 "{:?}..{:?}",
397 window.first().unwrap_or(&"".to_string()),
398 window.last().unwrap_or(&"".to_string())
399 ),
400 similarity: sim,
401 timestamp_ms: now,
402 matched_elements: window.to_vec(),
403 details: format!(
404 "Matched sequence pattern '{}' ({})",
405 pattern.name, pattern.domain
406 ),
407 });
408
409 if let Some(p) = crate::sync_guard::write_lock(&self.patterns)
411 .iter_mut()
412 .find(|p| p.id == pattern.id)
413 {
414 p.significance.frequency += 1;
415 p.significance.last_matched_ms = now;
416 p.significance.avg_similarity =
417 p.significance.avg_similarity * 0.7 + sim * 0.3;
418 p.significance.confidence = (p.significance.confidence + 0.05).min(1.0);
419 }
420 }
421 }
422 }
423 }
424
425 crate::sync_guard::write_lock(&self.match_history).extend(matches.clone());
426 matches
427 }
428
429 fn sequence_similarity(&self, a: &[String], b: &[String]) -> f64 {
431 if a.is_empty() && b.is_empty() {
432 return 1.0;
433 }
434 if a.is_empty() || b.is_empty() {
435 return 0.0;
436 }
437
438 let max_len = a.len().max(b.len()) as f64;
439 let mut matches = 0u64;
440
441 for i in 0..a.len().min(b.len()) {
442 if a[i] == b[i] {
443 matches += 1;
444 }
445 }
446
447 matches as f64 / max_len
448 }
449
450 pub fn find_temporal_patterns(
456 &self,
457 events: &[(u64, String, f64)],
458 domain: &str,
459 ) -> Vec<RecognizedPattern> {
460 if events.is_empty() {
461 return Vec::new();
462 }
463 let config = crate::sync_guard::read_lock(&self.config).clone();
464 let mut patterns = Vec::new();
465
466 let mut hourly: HashMap<(u8, u8), Vec<f64>> = HashMap::new(); for (ts, _id, intensity) in events {
470 let secs = *ts / 1000;
471 let hour = ((secs / 3600) % 24) as u8;
472 let day = ((secs / 86400) % 7) as u8;
473 hourly.entry((hour, day)).or_default().push(*intensity);
474 }
475
476 let mut peak_hours: Vec<(u8, u8, f64)> = hourly
479 .iter()
480 .map(|((h, d), intensities)| {
481 let avg: f64 = intensities.iter().sum::<f64>() / intensities.len() as f64;
482 (*h, *d, avg)
483 })
484 .filter(|(_, _, avg)| *avg > 0.5)
485 .collect();
486
487 peak_hours.sort_by(|a, b| b.2.total_cmp(&a.2));
488
489 if !peak_hours.is_empty() {
490 let slices: Vec<TemporalSlice> = peak_hours
491 .iter()
492 .map(|(h, d, avg)| TemporalSlice {
493 hour: *h,
494 day_of_week: *d,
495 frequency: 1.0,
496 avg_intensity: *avg,
497 activity_ids: Vec::new(),
498 })
499 .collect();
500
501 let confidence = (peak_hours.len() as f64 / 24.0).min(1.0) * 0.5;
502
503 let pattern = RecognizedPattern {
504 id: self.next_id(),
505 name: format!("temporal_peak_{}", domain),
506 pattern_type: PatternType::Temporal,
507 template: PatternTemplate::Temporal(slices),
508 significance: PatternSignificance {
509 confidence,
510 frequency: events.len() as u64,
511 last_matched_ms: self.now_ms(),
512 avg_similarity: confidence,
513 learning_rate: config.learning_rate,
514 },
515 tags: vec!["auto_detected".to_string(), "peak_hours".to_string()],
516 domain: domain.to_string(),
517 metadata: {
518 let mut m = HashMap::new();
519 m.insert("peak_count".to_string(), peak_hours.len().to_string());
520 m
521 },
522 };
523
524 let id = pattern.id;
525 crate::sync_guard::write_lock(&self.patterns).push(pattern.clone());
526 crate::sync_guard::write_lock(&self.domain_index)
527 .entry(domain.to_string())
528 .or_default()
529 .push(id);
530
531 patterns.push(pattern);
532 }
533
534 let day_parts: [(&str, u8, u8); 4] = [
536 ("reggel", 6, 12),
537 ("delutan", 12, 18),
538 ("este", 18, 23),
539 ("hajnal", 0, 6),
540 ];
541
542 for (part_name, start_h, end_h) in &day_parts {
543 let count: usize = hourly
544 .iter()
545 .filter(|((h, _), _)| *h >= *start_h && *h <= *end_h)
546 .map(|(_, v)| v.len())
547 .sum();
548
549 if count > 0 {
550 let total_events: usize = events.len();
551 let ratio = count as f64 / total_events as f64;
552 if ratio > 0.4 {
553 let slices: Vec<TemporalSlice> = hourly
554 .iter()
555 .filter(|((h, _), _)| *h >= *start_h && *h <= *end_h)
556 .map(|((h, d), intensities)| {
557 let avg = intensities.iter().sum::<f64>() / intensities.len() as f64;
558 TemporalSlice {
559 hour: *h,
560 day_of_week: *d,
561 frequency: intensities.len() as f64,
562 avg_intensity: avg,
563 activity_ids: Vec::new(),
564 }
565 })
566 .collect();
567
568 let pattern = RecognizedPattern {
569 id: self.next_id(),
570 name: format!("{}_{}", part_name, domain),
571 pattern_type: PatternType::Temporal,
572 template: PatternTemplate::Temporal(slices),
573 significance: PatternSignificance {
574 confidence: ratio * 0.6,
575 frequency: count as u64,
576 last_matched_ms: self.now_ms(),
577 avg_similarity: ratio,
578 learning_rate: config.learning_rate,
579 },
580 tags: vec!["auto_detected".to_string(), "daily_rhythm".to_string()],
581 domain: domain.to_string(),
582 metadata: HashMap::new(),
583 };
584
585 let id = pattern.id;
586 crate::sync_guard::write_lock(&self.patterns).push(pattern.clone());
587 crate::sync_guard::write_lock(&self.domain_index)
588 .entry(domain.to_string())
589 .or_default()
590 .push(id);
591
592 patterns.push(pattern);
593 }
594 }
595 }
596
597 patterns
598 }
599 pub fn find_motifs(
605 &self,
606 edges: &[(String, String, f64)],
607 domain: &str,
608 ) -> Vec<RecognizedPattern> {
609 let config = crate::sync_guard::read_lock(&self.config).clone();
610 let mut patterns = Vec::new();
611
612 if edges.is_empty() {
613 return patterns;
614 }
615
616 let mut fan_out: HashMap<String, Vec<String>> = HashMap::new();
619 for (from, to, _) in edges {
620 fan_out.entry(from.clone()).or_default().push(to.clone());
621 }
622
623 for (node, targets) in &fan_out {
624 if targets.len() >= 3 {
625 let mut motif_edges: Vec<(String, String, f64)> = Vec::new();
626 for t in targets {
627 motif_edges.push((node.clone(), t.clone(), 1.0));
628 }
629
630 let confidence = (targets.len() as f64 / 10.0).min(1.0);
631 let pattern = RecognizedPattern {
632 id: self.next_id(),
633 name: format!("fan_out_{}", node),
634 pattern_type: PatternType::Structural,
635 template: PatternTemplate::Structural(motif_edges),
636 significance: PatternSignificance {
637 confidence: confidence * 0.5,
638 frequency: targets.len() as u64,
639 last_matched_ms: self.now_ms(),
640 avg_similarity: confidence,
641 learning_rate: config.learning_rate,
642 },
643 tags: vec!["auto_detected".to_string(), "fan_out".to_string()],
644 domain: domain.to_string(),
645 metadata: {
646 let mut m = HashMap::new();
647 m.insert("node_count".to_string(), (targets.len() + 1).to_string());
648 m
649 },
650 };
651
652 let id = pattern.id;
653 crate::sync_guard::write_lock(&self.patterns).push(pattern.clone());
654 crate::sync_guard::write_lock(&self.domain_index)
655 .entry(domain.to_string())
656 .or_default()
657 .push(id);
658 patterns.push(pattern);
659 }
660 }
661
662 let mut fan_in: HashMap<String, Vec<String>> = HashMap::new();
664 for (from, to, _) in edges {
665 fan_in.entry(to.clone()).or_default().push(from.clone());
666 }
667
668 for (node, sources) in &fan_in {
669 if sources.len() >= 3 {
670 let mut motif_edges: Vec<(String, String, f64)> = Vec::new();
671 for s in sources {
672 motif_edges.push((s.clone(), node.clone(), 1.0));
673 }
674
675 let pattern = RecognizedPattern {
676 id: self.next_id(),
677 name: format!("fan_in_{}", node),
678 pattern_type: PatternType::Structural,
679 template: PatternTemplate::Structural(motif_edges),
680 significance: PatternSignificance {
681 confidence: (sources.len() as f64 / 10.0).min(1.0) * 0.5,
682 frequency: sources.len() as u64,
683 last_matched_ms: self.now_ms(),
684 avg_similarity: 0.0,
685 learning_rate: config.learning_rate,
686 },
687 tags: vec!["auto_detected".to_string(), "fan_in".to_string()],
688 domain: domain.to_string(),
689 metadata: HashMap::new(),
690 };
691
692 let id = pattern.id;
693 crate::sync_guard::write_lock(&self.patterns).push(pattern.clone());
694 crate::sync_guard::write_lock(&self.domain_index)
695 .entry(domain.to_string())
696 .or_default()
697 .push(id);
698 patterns.push(pattern);
699 }
700 }
701
702 let mut degrees: HashMap<String, usize> = HashMap::new();
704 for (from, to, _) in edges {
705 *degrees.entry(from.clone()).or_insert(0) += 1;
706 *degrees.entry(to.clone()).or_insert(0) += 1;
707 }
708
709 let avg_degree = degrees.values().sum::<usize>() as f64 / degrees.len().max(1) as f64;
710 for (node, degree) in °rees {
711 if *degree as f64 > avg_degree * 2.0 && *degree >= 4 {
712 let pattern = RecognizedPattern {
713 id: self.next_id(),
714 name: format!("hub_{}", node),
715 pattern_type: PatternType::Structural,
716 template: PatternTemplate::Structural(vec![(
717 node.clone(),
718 "__hub__".to_string(),
719 *degree as f64,
720 )]),
721 significance: PatternSignificance {
722 confidence: (*degree as f64 / 20.0).min(1.0) * 0.4,
723 frequency: *degree as u64,
724 last_matched_ms: self.now_ms(),
725 avg_similarity: 0.0,
726 learning_rate: config.learning_rate,
727 },
728 tags: vec!["auto_detected".to_string(), "hub".to_string()],
729 domain: domain.to_string(),
730 metadata: {
731 let mut m = HashMap::new();
732 m.insert("degree".to_string(), degree.to_string());
733 m.insert("avg_degree".to_string(), format!("{:.1}", avg_degree));
734 m
735 },
736 };
737
738 let id = pattern.id;
739 crate::sync_guard::write_lock(&self.patterns).push(pattern.clone());
740 crate::sync_guard::write_lock(&self.domain_index)
741 .entry(domain.to_string())
742 .or_default()
743 .push(id);
744 patterns.push(pattern);
745 }
746 }
747
748 patterns
749 }
750
751 pub fn find_motifs_in_organism(
753 &self,
754 _nodes: &[crate::morphogenesis::MorphNode],
755 connections: &[crate::morphogenesis::MorphConnection],
756 ) -> Vec<RecognizedPattern> {
757 let edges: Vec<(String, String, f64)> = connections
758 .iter()
759 .filter(|c| c.is_active)
760 .map(|c| (c.from_node.to_string(), c.to_node.to_string(), c.weight))
761 .collect();
762
763 self.find_motifs(&edges, "morphogenesis")
764 }
765
766 pub fn find_clusters(
772 &self,
773 points: &[(f64, f64, f64, String)],
774 domain: &str,
775 ) -> Vec<RecognizedPattern> {
776 let config = crate::sync_guard::read_lock(&self.config).clone();
777 let mut patterns = Vec::new();
778
779 if points.is_empty() {
780 return patterns;
781 }
782
783 let eps = config.cluster_radius;
784 let min_pts = 3;
785 let mut visited: HashSet<usize> = HashSet::new();
786 let mut clusters: Vec<Vec<usize>> = Vec::new();
787
788 for i in 0..points.len() {
789 if visited.contains(&i) {
790 continue;
791 }
792
793 let mut neighbors: Vec<usize> = points
794 .iter()
795 .enumerate()
796 .filter(|(j, _)| {
797 let dist = ((points[i].0 - points[*j].0).powi(2)
798 + (points[i].1 - points[*j].1).powi(2)
799 + (points[i].2 - points[*j].2).powi(2))
800 .sqrt();
801 dist <= eps
802 })
803 .map(|(j, _)| j)
804 .collect();
805
806 if neighbors.len() < min_pts {
807 visited.insert(i);
808 continue; }
810
811 let mut cluster: Vec<usize> = Vec::new();
813 while let Some(seed) = neighbors.pop() {
814 if visited.contains(&seed) {
815 continue;
816 }
817 visited.insert(seed);
818 cluster.push(seed);
819
820 let seed_neighbors: Vec<usize> = points
821 .iter()
822 .enumerate()
823 .filter(|(j, _)| !visited.contains(j))
824 .filter(|(j, _)| {
825 let dist = ((points[seed].0 - points[*j].0).powi(2)
826 + (points[seed].1 - points[*j].1).powi(2)
827 + (points[seed].2 - points[*j].2).powi(2))
828 .sqrt();
829 dist <= eps
830 })
831 .map(|(j, _)| j)
832 .collect();
833
834 neighbors.extend(seed_neighbors);
835 }
836
837 if cluster.len() >= min_pts {
838 clusters.push(cluster);
839 }
840 }
841
842 for (ci, cluster) in clusters.iter().enumerate() {
844 let cluster_points: Vec<&(f64, f64, f64, String)> =
845 cluster.iter().map(|&i| &points[i]).collect();
846
847 let cx = cluster_points.iter().map(|p| p.0).sum::<f64>() / cluster_points.len() as f64;
849 let cy = cluster_points.iter().map(|p| p.1).sum::<f64>() / cluster_points.len() as f64;
850 let cz = cluster_points.iter().map(|p| p.2).sum::<f64>() / cluster_points.len() as f64;
851
852 let avg_dist: f64 = cluster_points
854 .iter()
855 .map(|p| ((p.0 - cx).powi(2) + (p.1 - cy).powi(2) + (p.2 - cz).powi(2)).sqrt())
856 .sum::<f64>()
857 / cluster_points.len() as f64;
858
859 let element_ids: Vec<String> = cluster_points.iter().map(|p| p.3.clone()).collect();
860 let density =
861 cluster.len() as f64 / (avg_dist * avg_dist * 4.0 * std::f64::consts::PI).max(0.01);
862
863 let pattern = RecognizedPattern {
864 id: self.next_id(),
865 name: format!("cluster_{}_{}", domain, ci),
866 pattern_type: PatternType::Cluster,
867 template: PatternTemplate::Cluster {
868 center: (cx, cy, cz),
869 radius: avg_dist,
870 elements: element_ids.clone(),
871 },
872 significance: PatternSignificance {
873 confidence: (density / 100.0).min(1.0),
874 frequency: element_ids.len() as u64,
875 last_matched_ms: self.now_ms(),
876 avg_similarity: 1.0 - (avg_dist / 1.0).min(1.0),
877 learning_rate: config.learning_rate,
878 },
879 tags: vec!["auto_detected".to_string(), "cluster".to_string()],
880 domain: domain.to_string(),
881 metadata: {
882 let mut m = HashMap::new();
883 m.insert("size".to_string(), cluster.len().to_string());
884 m.insert("density".to_string(), format!("{:.3}", density));
885 m
886 },
887 };
888
889 let id = pattern.id;
890 crate::sync_guard::write_lock(&self.patterns).push(pattern.clone());
891 crate::sync_guard::write_lock(&self.domain_index)
892 .entry(domain.to_string())
893 .or_default()
894 .push(id);
895 patterns.push(pattern);
896 }
897
898 patterns
899 }
900 pub fn cross_correlate(&self) -> Vec<RecognizedPattern> {
906 let config = crate::sync_guard::read_lock(&self.config).clone();
907 let mut composite_patterns = Vec::new();
908
909 let patterns = crate::sync_guard::read_lock(&self.patterns);
910 let domains: Vec<String> = crate::sync_guard::read_lock(&self.domain_index)
911 .keys()
912 .cloned()
913 .collect();
914
915 if domains.len() < 2 {
916 return Vec::new();
917 }
918
919 for i in 0..domains.len() {
921 for j in (i + 1)..domains.len() {
922 let d1 = &domains[i];
923 let d2 = &domains[j];
924
925 let p1: Vec<&RecognizedPattern> =
926 patterns.iter().filter(|p| p.domain == *d1).collect();
927 let p2: Vec<&RecognizedPattern> =
928 patterns.iter().filter(|p| p.domain == *d2).collect();
929
930 for pat1 in &p1 {
931 for pat2 in &p2 {
932 if pat1.pattern_type != pat2.pattern_type {
934 continue;
935 }
936
937 let sim = match (&pat1.template, &pat2.template) {
938 (PatternTemplate::Sequence(s1), PatternTemplate::Sequence(s2)) => {
939 self.sequence_similarity(s1, s2)
940 }
941 (PatternTemplate::Structural(e1), PatternTemplate::Structural(e2)) => {
942 let common = e1.iter().filter(|e| e2.contains(e)).count();
943 let max = e1.len().max(e2.len()).max(1);
944 common as f64 / max as f64
945 }
946 _ => 0.0,
947 };
948
949 if sim > config.min_similarity * 1.2 {
950 let composite_id = self.next_id();
952 let composite = RecognizedPattern {
953 id: composite_id,
954 name: format!("cross_{}_{}", pat1.name, pat2.name),
955 pattern_type: PatternType::CrossDomain,
956 template: PatternTemplate::Composite(vec![pat1.id, pat2.id]),
957 significance: PatternSignificance {
958 confidence: sim * 0.8,
959 frequency: pat1.significance.frequency
960 + pat2.significance.frequency,
961 last_matched_ms: self.now_ms(),
962 avg_similarity: sim,
963 learning_rate: config.learning_rate,
964 },
965 tags: vec!["auto_detected".to_string(), "cross_domain".to_string()],
966 domain: format!("cross:{}:{}", d1, d2),
967 metadata: {
968 let mut m = HashMap::new();
969 m.insert("domain_a".to_string(), d1.clone());
970 m.insert("domain_b".to_string(), d2.clone());
971 m
972 },
973 };
974
975 composite_patterns.push(composite);
976 }
977 }
978 }
979 }
980 }
981
982 for cp in &composite_patterns {
984 let id = cp.id;
985 crate::sync_guard::write_lock(&self.patterns).push(cp.clone());
986 crate::sync_guard::write_lock(&self.domain_index)
987 .entry(cp.domain.clone())
988 .or_default()
989 .push(id);
990 }
991
992 composite_patterns
993 }
994
995 pub fn consolidate(&self) -> usize {
999 let config = crate::sync_guard::read_lock(&self.config).clone();
1000 let mut patterns = crate::sync_guard::write_lock(&self.patterns);
1001 let before = patterns.len();
1002
1003 patterns.retain(|p| {
1005 p.significance.confidence >= config.min_confidence
1006 || p.significance.frequency >= config.confirmation_threshold
1007 });
1008
1009 let mut i = 0;
1011 while i < patterns.len() {
1012 let mut j = i + 1;
1013 while j < patterns.len() {
1014 let should_merge = patterns[i].pattern_type == patterns[j].pattern_type
1015 && patterns[i].domain == patterns[j].domain
1016 && match (&patterns[i].template, &patterns[j].template) {
1017 (PatternTemplate::Sequence(s1), PatternTemplate::Sequence(s2)) => {
1018 self.sequence_similarity(s1, s2) > 0.8
1019 }
1020 _ => false,
1021 };
1022
1023 if should_merge {
1024 patterns[i].significance.frequency += patterns[j].significance.frequency;
1025 patterns[i].significance.confidence = patterns[i]
1026 .significance
1027 .confidence
1028 .max(patterns[j].significance.confidence);
1029 patterns.remove(j);
1030 } else {
1031 j += 1;
1032 }
1033 }
1034 i += 1;
1035 }
1036
1037 while patterns.len() > config.max_patterns {
1039 let min_pos = (0..patterns.len())
1041 .min_by(|&a, &b| {
1042 patterns[a]
1043 .significance
1044 .confidence
1045 .partial_cmp(&patterns[b].significance.confidence)
1046 .unwrap()
1047 })
1048 .unwrap_or(0);
1049 patterns.remove(min_pos);
1050 }
1051
1052 before - patterns.len()
1053 }
1054
1055 pub fn stats(&self) -> (usize, usize, usize, f64) {
1059 let patterns = crate::sync_guard::read_lock(&self.patterns);
1060
1061 let total = patterns.len();
1062 let seq_count = patterns
1063 .iter()
1064 .filter(|p| p.pattern_type == PatternType::Sequence)
1065 .count();
1066 let struct_count = patterns
1067 .iter()
1068 .filter(|p| p.pattern_type == PatternType::Structural)
1069 .count();
1070 let avg_confidence = if total > 0 {
1071 patterns
1072 .iter()
1073 .map(|p| p.significance.confidence)
1074 .sum::<f64>()
1075 / total as f64
1076 } else {
1077 0.0
1078 };
1079
1080 (total, seq_count, struct_count, avg_confidence)
1081 }
1082
1083 pub fn recent_matches(&self, k: usize) -> Vec<PatternMatch> {
1085 crate::sync_guard::read_lock(&self.match_history)
1086 .iter()
1087 .rev()
1088 .take(k)
1089 .cloned()
1090 .collect()
1091 }
1092
1093 pub fn set_config(&self, config: RecognitionConfig) {
1095 *crate::sync_guard::write_lock(&self.config) = config;
1096 }
1097
1098 pub fn get_config(&self) -> RecognitionConfig {
1100 crate::sync_guard::read_lock(&self.config).clone()
1101 }
1102
1103 pub fn list_domains(&self) -> Vec<String> {
1105 crate::sync_guard::read_lock(&self.domain_index)
1106 .keys()
1107 .cloned()
1108 .collect()
1109 }
1110
1111 pub fn domain_pattern_count(&self, domain: &str) -> usize {
1113 crate::sync_guard::read_lock(&self.domain_index)
1114 .get(domain)
1115 .map(|ids| ids.len())
1116 .unwrap_or(0)
1117 }
1118}
1119
1120impl std::fmt::Display for PatternType {
1124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1125 match self {
1126 PatternType::Sequence => write!(f, "Sequence"),
1127 PatternType::Temporal => write!(f, "Temporal"),
1128 PatternType::Structural => write!(f, "Structural"),
1129 PatternType::Cluster => write!(f, "Cluster"),
1130 PatternType::CrossDomain => write!(f, "CrossDomain"),
1131 PatternType::Unknown => write!(f, "Unknown"),
1132 }
1133 }
1134}
1135
1136impl std::fmt::Display for RecognizedPattern {
1137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1138 write!(
1139 f,
1140 "Pattern #{} [{}] '{}' — {} ({:.1}% confidence, {} freq)",
1141 self.id,
1142 self.pattern_type,
1143 self.name,
1144 self.domain,
1145 self.significance.confidence * 100.0,
1146 self.significance.frequency
1147 )
1148 }
1149}