1use super::error::{MemoryError, Result};
43use super::models::*;
44use super::repository::MemoryRepository;
45use chrono::{DateTime, Duration, Utc};
46use pgvector::Vector;
47use serde::{Deserialize, Serialize};
48use std::collections::{HashMap, HashSet, VecDeque};
49use std::sync::Arc;
50use tracing::{debug, info, warn};
51use uuid::Uuid;
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ReflectionConfig {
56 pub importance_trigger_threshold: f64,
58
59 pub max_memories_per_reflection: usize,
61
62 pub target_insights_per_reflection: usize,
64
65 pub clustering_similarity_threshold: f64,
67
68 pub insight_importance_multiplier: f64,
70
71 pub max_graph_depth: usize,
73
74 pub min_cluster_size: usize,
76
77 pub temporal_analysis_window_days: i64,
79
80 pub reflection_cooldown_hours: i64,
82}
83
84impl Default for ReflectionConfig {
85 fn default() -> Self {
86 Self {
87 importance_trigger_threshold: 150.0,
88 max_memories_per_reflection: 100,
89 target_insights_per_reflection: 3,
90 clustering_similarity_threshold: 0.75,
91 insight_importance_multiplier: 1.5,
92 max_graph_depth: 3,
93 min_cluster_size: 3,
94 temporal_analysis_window_days: 30,
95 reflection_cooldown_hours: 6,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
102pub enum InsightType {
103 Pattern,
105 Synthesis,
107 Gap,
109 Contradiction,
111 Trend,
113 Causality,
115 Analogy,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct Insight {
122 pub id: Uuid,
123 pub insight_type: InsightType,
124 pub content: String,
125 pub confidence_score: f64,
126 pub source_memory_ids: Vec<Uuid>,
127 pub related_concepts: Vec<String>,
128 pub knowledge_graph_nodes: Vec<KnowledgeNode>,
129 pub importance_score: f64,
130 pub generated_at: DateTime<Utc>,
131 pub validation_metrics: ValidationMetrics,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct ValidationMetrics {
137 pub novelty_score: f64,
138 pub coherence_score: f64,
139 pub evidence_strength: f64,
140 pub semantic_richness: f64,
141 pub predictive_power: f64,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct KnowledgeNode {
147 pub id: Uuid,
148 pub concept: String,
149 pub node_type: NodeType,
150 #[serde(skip)]
151 pub embedding: Option<Vector>,
152 pub confidence: f64,
153 pub connections: Vec<KnowledgeEdge>,
154 pub created_at: DateTime<Utc>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
159pub enum NodeType {
160 Concept,
161 Entity,
162 Relationship,
163 Insight,
164 Memory,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct KnowledgeEdge {
170 pub target_node_id: Uuid,
171 pub relationship_type: RelationshipType,
172 pub strength: f64,
173 pub evidence_memories: Vec<Uuid>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
178pub enum RelationshipType {
179 IsA,
180 PartOf,
181 CausedBy,
182 SimilarTo,
183 ConflictsWith,
184 Enables,
185 Requires,
186 Exemplifies,
187 GeneralizedBy,
188 TemporallyPrecedes,
189}
190
191#[derive(Debug, Clone)]
193pub struct MemoryCluster {
194 pub id: Uuid,
195 pub memories: Vec<Memory>,
196 pub centroid_embedding: Option<Vector>,
197 pub coherence_score: f64,
198 pub dominant_concepts: Vec<String>,
199 pub temporal_span: Option<(DateTime<Utc>, DateTime<Utc>)>,
200}
201
202#[derive(Debug, Clone)]
204pub struct ReflectionSession {
205 pub id: Uuid,
206 pub started_at: DateTime<Utc>,
207 pub trigger_reason: String,
208 pub analyzed_memories: Vec<Memory>,
209 pub generated_clusters: Vec<MemoryCluster>,
210 pub generated_insights: Vec<Insight>,
211 pub knowledge_graph_updates: Vec<KnowledgeNode>,
212 pub completion_status: ReflectionStatus,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
217pub enum ReflectionStatus {
218 InProgress,
219 Completed,
220 Failed,
221 Cancelled,
222}
223
224pub struct ReflectionEngine {
226 config: ReflectionConfig,
227 repository: Arc<MemoryRepository>,
228 #[allow(dead_code)]
229 knowledge_graph: KnowledgeGraph,
230 last_reflection_time: Option<DateTime<Utc>>,
231}
232
233impl ReflectionEngine {
234 pub fn new(config: ReflectionConfig, repository: Arc<MemoryRepository>) -> Self {
235 Self {
236 config,
237 repository,
238 knowledge_graph: KnowledgeGraph::new(),
239 last_reflection_time: None,
240 }
241 }
242
243 pub async fn should_trigger_reflection(&self) -> Result<Option<String>> {
245 if let Some(last_time) = self.last_reflection_time {
247 let hours_since_last = Utc::now().signed_duration_since(last_time).num_hours();
248 if hours_since_last < self.config.reflection_cooldown_hours {
249 return Ok(None);
250 }
251 }
252
253 let cutoff_time = self
255 .last_reflection_time
256 .unwrap_or(Utc::now() - Duration::days(1));
257
258 let recent_memories = self.get_recent_memories_since(cutoff_time).await?;
259 let total_importance: f64 = recent_memories.iter().map(|m| m.importance_score).sum();
260
261 if total_importance >= self.config.importance_trigger_threshold {
262 return Ok(Some(format!(
263 "Importance threshold reached: {:.1} >= {:.1}",
264 total_importance, self.config.importance_trigger_threshold
265 )));
266 }
267
268 if let Some(cluster_reason) = self.check_cluster_density(&recent_memories).await? {
270 return Ok(Some(cluster_reason));
271 }
272
273 if let Some(pattern_reason) = self.check_temporal_patterns(&recent_memories).await? {
275 return Ok(Some(pattern_reason));
276 }
277
278 Ok(None)
279 }
280
281 pub async fn execute_reflection(
283 &mut self,
284 trigger_reason: String,
285 ) -> Result<ReflectionSession> {
286 let session_id = Uuid::new_v4();
287 let start_time = Utc::now();
288
289 info!(
290 "Starting reflection session {}: {}",
291 session_id, trigger_reason
292 );
293
294 let mut session = ReflectionSession {
295 id: session_id,
296 started_at: start_time,
297 trigger_reason: trigger_reason.clone(),
298 analyzed_memories: Vec::new(),
299 generated_clusters: Vec::new(),
300 generated_insights: Vec::new(),
301 knowledge_graph_updates: Vec::new(),
302 completion_status: ReflectionStatus::InProgress,
303 };
304
305 match self.execute_reflection_pipeline(&mut session).await {
306 Ok(_) => {
307 session.completion_status = ReflectionStatus::Completed;
308 self.last_reflection_time = Some(Utc::now());
309
310 info!(
311 "Reflection session {} completed: {} insights generated from {} memories",
312 session_id,
313 session.generated_insights.len(),
314 session.analyzed_memories.len()
315 );
316 }
317 Err(e) => {
318 session.completion_status = ReflectionStatus::Failed;
319 warn!("Reflection session {} failed: {}", session_id, e);
320 return Err(e);
321 }
322 }
323
324 Ok(session)
325 }
326
327 async fn execute_reflection_pipeline(&self, session: &mut ReflectionSession) -> Result<()> {
329 session.analyzed_memories = self.gather_reflection_memories().await?;
331
332 if session.analyzed_memories.is_empty() {
333 return Err(MemoryError::InvalidRequest {
334 message: "No memories available for reflection".to_string(),
335 });
336 }
337
338 session.generated_clusters = self.cluster_memories(&session.analyzed_memories).await?;
340
341 for cluster in &session.generated_clusters {
343 let cluster_insights = self.generate_cluster_insights(cluster).await?;
344 session.generated_insights.extend(cluster_insights);
345 }
346
347 let cross_cluster_insights = self
349 .generate_cross_cluster_insights(&session.generated_clusters)
350 .await?;
351 session.generated_insights.extend(cross_cluster_insights);
352
353 session.knowledge_graph_updates = self
355 .update_knowledge_graph(&session.generated_insights)
356 .await?;
357
358 self.store_insights_as_memories(&session.generated_insights)
360 .await?;
361
362 self.validate_and_prune_insights(&mut session.generated_insights)
364 .await?;
365
366 Ok(())
367 }
368
369 async fn gather_reflection_memories(&self) -> Result<Vec<Memory>> {
371 let cutoff_time = self
372 .last_reflection_time
373 .unwrap_or(Utc::now() - Duration::days(self.config.temporal_analysis_window_days));
374
375 let search_request = SearchRequest {
377 date_range: Some(DateRange {
378 start: Some(cutoff_time),
379 end: Some(Utc::now()),
380 }),
381 importance_range: Some(RangeFilter {
382 min: Some(0.3),
383 max: None,
384 }),
385 limit: Some(self.config.max_memories_per_reflection as i32),
386 search_type: Some(SearchType::Temporal),
387 ..Default::default()
388 };
389
390 let search_response = self.repository.search_memories(search_request).await?;
391
392 Ok(search_response
393 .results
394 .into_iter()
395 .map(|result| result.memory)
396 .collect())
397 }
398
399 async fn cluster_memories(&self, memories: &[Memory]) -> Result<Vec<MemoryCluster>> {
401 let mut clusters = Vec::new();
402 let mut unassigned_memories: Vec<_> = memories.iter().collect();
403
404 while !unassigned_memories.is_empty() {
405 let seed_memory = unassigned_memories.remove(0);
406 let mut cluster_memories = vec![seed_memory.clone()];
407
408 let mut i = 0;
410 while i < unassigned_memories.len() {
411 let memory = unassigned_memories[i];
412
413 if let (Some(seed_embedding), Some(memory_embedding)) =
414 (&seed_memory.embedding, &memory.embedding)
415 {
416 let similarity =
417 self.calculate_cosine_similarity(seed_embedding, memory_embedding)?;
418
419 if similarity >= self.config.clustering_similarity_threshold {
420 cluster_memories.push(memory.clone());
421 unassigned_memories.remove(i);
422 } else {
423 i += 1;
424 }
425 } else {
426 i += 1;
427 }
428 }
429
430 if cluster_memories.len() >= self.config.min_cluster_size {
432 let cluster = self.create_memory_cluster(cluster_memories).await?;
433 clusters.push(cluster);
434 }
435 }
436
437 Ok(clusters)
438 }
439
440 async fn create_memory_cluster(&self, memories: Vec<Memory>) -> Result<MemoryCluster> {
442 let cluster_id = Uuid::new_v4();
443
444 let centroid_embedding = self.calculate_centroid_embedding(&memories)?;
446
447 let coherence_score = self.calculate_cluster_coherence(&memories)?;
449
450 let dominant_concepts = self.extract_dominant_concepts(&memories).await?;
452
453 let temporal_span = self.calculate_temporal_span(&memories);
455
456 Ok(MemoryCluster {
457 id: cluster_id,
458 memories,
459 centroid_embedding,
460 coherence_score,
461 dominant_concepts,
462 temporal_span,
463 })
464 }
465
466 async fn generate_cluster_insights(&self, cluster: &MemoryCluster) -> Result<Vec<Insight>> {
468 let mut insights = Vec::new();
469
470 if let Some(pattern_insight) = self.detect_cluster_patterns(cluster).await? {
472 insights.push(pattern_insight);
473 }
474
475 if let Some(synthesis_insight) = self.generate_synthesis_insight(cluster).await? {
477 insights.push(synthesis_insight);
478 }
479
480 if let Some(trend_insight) = self.detect_temporal_trends(cluster).await? {
482 insights.push(trend_insight);
483 }
484
485 if let Some(gap_insight) = self.identify_knowledge_gaps(cluster).await? {
487 insights.push(gap_insight);
488 }
489
490 Ok(insights)
491 }
492
493 async fn generate_cross_cluster_insights(
495 &self,
496 clusters: &[MemoryCluster],
497 ) -> Result<Vec<Insight>> {
498 let mut insights = Vec::new();
499
500 for i in 0..clusters.len() {
502 for j in i + 1..clusters.len() {
503 if let Some(analogy_insight) = self
504 .detect_cross_cluster_analogies(&clusters[i], &clusters[j])
505 .await?
506 {
507 insights.push(analogy_insight);
508 }
509 }
510 }
511
512 let causal_insights = self.detect_causal_relationships(clusters).await?;
514 insights.extend(causal_insights);
515
516 Ok(insights)
517 }
518
519 async fn update_knowledge_graph(&self, insights: &[Insight]) -> Result<Vec<KnowledgeNode>> {
521 let mut new_nodes = Vec::new();
522
523 for insight in insights {
524 let insight_node = KnowledgeNode {
526 id: insight.id,
527 concept: insight.content.clone(),
528 node_type: NodeType::Insight,
529 embedding: None, confidence: insight.confidence_score,
531 connections: Vec::new(),
532 created_at: insight.generated_at,
533 };
534
535 new_nodes.push(insight_node);
536
537 for concept in &insight.related_concepts {
539 let concept_node = KnowledgeNode {
540 id: Uuid::new_v4(),
541 concept: concept.clone(),
542 node_type: NodeType::Concept,
543 embedding: None,
544 confidence: 0.8,
545 connections: vec![KnowledgeEdge {
546 target_node_id: insight.id,
547 relationship_type: RelationshipType::Exemplifies,
548 strength: 0.9,
549 evidence_memories: insight.source_memory_ids.clone(),
550 }],
551 created_at: Utc::now(),
552 };
553
554 new_nodes.push(concept_node);
555 }
556 }
557
558 Ok(new_nodes)
559 }
560
561 async fn store_insights_as_memories(&self, insights: &[Insight]) -> Result<()> {
563 for insight in insights {
564 let importance_score =
565 insight.importance_score * self.config.insight_importance_multiplier;
566 let importance_score = importance_score.min(1.0); let metadata = serde_json::json!({
569 "insight_type": insight.insight_type,
570 "confidence_score": insight.confidence_score,
571 "source_memory_ids": insight.source_memory_ids,
572 "related_concepts": insight.related_concepts,
573 "validation_metrics": insight.validation_metrics,
574 "is_meta_memory": true,
575 "generated_by": "reflection_engine"
576 });
577
578 let create_request = CreateMemoryRequest {
579 content: insight.content.clone(),
580 embedding: None, tier: Some(MemoryTier::Working), importance_score: Some(importance_score),
583 metadata: Some(metadata),
584 parent_id: None,
585 expires_at: None,
586 };
587
588 match self.repository.create_memory(create_request).await {
589 Ok(memory) => {
590 debug!("Stored insight {} as memory {}", insight.id, memory.id);
591 }
592 Err(e) => {
593 warn!("Failed to store insight {} as memory: {}", insight.id, e);
594 }
595 }
596 }
597
598 Ok(())
599 }
600
601 async fn validate_and_prune_insights(&self, insights: &mut Vec<Insight>) -> Result<()> {
603 let mut validated_insights = Vec::new();
604 let mut seen_concepts = HashSet::new();
605
606 for insight in insights.iter() {
607 let concept_hash = self.hash_insight_concepts(insight);
609 if seen_concepts.contains(&concept_hash) {
610 debug!("Pruning duplicate insight: {}", insight.content);
611 continue;
612 }
613
614 if self.validate_insight_quality(insight).await? {
616 seen_concepts.insert(concept_hash);
617 validated_insights.push(insight.clone());
618 } else {
619 debug!("Pruning low-quality insight: {}", insight.content);
620 }
621 }
622
623 *insights = validated_insights;
624 Ok(())
625 }
626
627 async fn detect_cluster_patterns(&self, _cluster: &MemoryCluster) -> Result<Option<Insight>> {
629 Ok(None)
632 }
633
634 async fn generate_synthesis_insight(
635 &self,
636 _cluster: &MemoryCluster,
637 ) -> Result<Option<Insight>> {
638 Ok(None)
640 }
641
642 async fn detect_temporal_trends(&self, _cluster: &MemoryCluster) -> Result<Option<Insight>> {
643 Ok(None)
645 }
646
647 async fn identify_knowledge_gaps(&self, _cluster: &MemoryCluster) -> Result<Option<Insight>> {
648 Ok(None)
650 }
651
652 async fn detect_cross_cluster_analogies(
653 &self,
654 _cluster1: &MemoryCluster,
655 _cluster2: &MemoryCluster,
656 ) -> Result<Option<Insight>> {
657 Ok(None)
659 }
660
661 async fn detect_causal_relationships(
662 &self,
663 _clusters: &[MemoryCluster],
664 ) -> Result<Vec<Insight>> {
665 Ok(Vec::new())
667 }
668
669 async fn get_recent_memories_since(&self, cutoff_time: DateTime<Utc>) -> Result<Vec<Memory>> {
671 let search_request = SearchRequest {
672 date_range: Some(DateRange {
673 start: Some(cutoff_time),
674 end: Some(Utc::now()),
675 }),
676 search_type: Some(SearchType::Temporal),
677 limit: Some(1000),
678 ..Default::default()
679 };
680
681 let response = self.repository.search_memories(search_request).await?;
682 Ok(response.results.into_iter().map(|r| r.memory).collect())
683 }
684
685 async fn check_cluster_density(&self, _memories: &[Memory]) -> Result<Option<String>> {
686 Ok(None)
688 }
689
690 async fn check_temporal_patterns(&self, _memories: &[Memory]) -> Result<Option<String>> {
691 Ok(None)
693 }
694
695 fn calculate_cosine_similarity(&self, vec1: &Vector, vec2: &Vector) -> Result<f64> {
696 let slice1 = vec1.as_slice();
697 let slice2 = vec2.as_slice();
698
699 if slice1.len() != slice2.len() {
700 return Ok(0.0);
701 }
702
703 let dot_product: f64 = slice1
704 .iter()
705 .zip(slice2.iter())
706 .map(|(a, b)| (*a as f64) * (*b as f64))
707 .sum();
708
709 let norm1: f64 = slice1
710 .iter()
711 .map(|x| (*x as f64).powi(2))
712 .sum::<f64>()
713 .sqrt();
714 let norm2: f64 = slice2
715 .iter()
716 .map(|x| (*x as f64).powi(2))
717 .sum::<f64>()
718 .sqrt();
719
720 if norm1 == 0.0 || norm2 == 0.0 {
721 return Ok(0.0);
722 }
723
724 Ok(dot_product / (norm1 * norm2))
725 }
726
727 fn calculate_centroid_embedding(&self, memories: &[Memory]) -> Result<Option<Vector>> {
728 let embeddings: Vec<_> = memories
729 .iter()
730 .filter_map(|m| m.embedding.as_ref())
731 .collect();
732
733 if embeddings.is_empty() {
734 return Ok(None);
735 }
736
737 let dim = embeddings[0].as_slice().len();
738 let mut centroid = vec![0.0f32; dim];
739
740 for embedding in &embeddings {
741 for (i, &val) in embedding.as_slice().iter().enumerate() {
742 centroid[i] += val;
743 }
744 }
745
746 for val in &mut centroid {
747 *val /= embeddings.len() as f32;
748 }
749
750 Ok(Some(Vector::from(centroid)))
751 }
752
753 fn calculate_cluster_coherence(&self, memories: &[Memory]) -> Result<f64> {
754 let embeddings: Vec<_> = memories
755 .iter()
756 .filter_map(|m| m.embedding.as_ref())
757 .collect();
758
759 if embeddings.len() < 2 {
760 return Ok(1.0);
761 }
762
763 let mut total_similarity = 0.0;
764 let mut pair_count = 0;
765
766 for i in 0..embeddings.len() {
767 for j in i + 1..embeddings.len() {
768 let similarity = self.calculate_cosine_similarity(embeddings[i], embeddings[j])?;
769 total_similarity += similarity;
770 pair_count += 1;
771 }
772 }
773
774 Ok(if pair_count > 0 {
775 total_similarity / pair_count as f64
776 } else {
777 0.0
778 })
779 }
780
781 async fn extract_dominant_concepts(&self, _memories: &[Memory]) -> Result<Vec<String>> {
782 Ok(vec!["concept1".to_string(), "concept2".to_string()])
784 }
785
786 fn calculate_temporal_span(
787 &self,
788 memories: &[Memory],
789 ) -> Option<(DateTime<Utc>, DateTime<Utc>)> {
790 if memories.is_empty() {
791 return None;
792 }
793
794 let mut min_time = memories[0].created_at;
795 let mut max_time = memories[0].created_at;
796
797 for memory in memories {
798 if memory.created_at < min_time {
799 min_time = memory.created_at;
800 }
801 if memory.created_at > max_time {
802 max_time = memory.created_at;
803 }
804 }
805
806 Some((min_time, max_time))
807 }
808
809 fn hash_insight_concepts(&self, insight: &Insight) -> u64 {
810 use std::collections::hash_map::DefaultHasher;
811 use std::hash::{Hash, Hasher};
812
813 let mut hasher = DefaultHasher::new();
814 insight.related_concepts.hash(&mut hasher);
815 insight.insight_type.hash(&mut hasher);
816 hasher.finish()
817 }
818
819 async fn validate_insight_quality(&self, insight: &Insight) -> Result<bool> {
820 let metrics = &insight.validation_metrics;
822
823 Ok(metrics.novelty_score > 0.3
825 && metrics.coherence_score > 0.5
826 && metrics.evidence_strength > 0.4
827 && insight.confidence_score > 0.6)
828 }
829}
830
831pub struct KnowledgeGraph {
833 nodes: HashMap<Uuid, KnowledgeNode>,
834 concept_index: HashMap<String, Vec<Uuid>>,
835}
836
837impl KnowledgeGraph {
838 pub fn new() -> Self {
839 Self {
840 nodes: HashMap::new(),
841 concept_index: HashMap::new(),
842 }
843 }
844
845 pub fn add_node(&mut self, node: KnowledgeNode) {
846 self.concept_index
848 .entry(node.concept.clone())
849 .or_insert_with(Vec::new)
850 .push(node.id);
851
852 self.nodes.insert(node.id, node);
853 }
854
855 pub fn find_related_concepts(&self, concept: &str, max_depth: usize) -> Vec<Uuid> {
856 let mut related = Vec::new();
857 let mut visited = HashSet::new();
858 let mut queue = VecDeque::new();
859
860 if let Some(direct_matches) = self.concept_index.get(concept) {
862 for &node_id in direct_matches {
863 queue.push_back((node_id, 0));
864 }
865 }
866
867 while let Some((node_id, depth)) = queue.pop_front() {
869 if depth >= max_depth || visited.contains(&node_id) {
870 continue;
871 }
872
873 visited.insert(node_id);
874 related.push(node_id);
875
876 if let Some(node) = self.nodes.get(&node_id) {
877 for edge in &node.connections {
878 if !visited.contains(&edge.target_node_id) {
879 queue.push_back((edge.target_node_id, depth + 1));
880 }
881 }
882 }
883 }
884
885 related
886 }
887}
888
889impl Default for KnowledgeGraph {
890 fn default() -> Self {
891 Self::new()
892 }
893}
894
895#[cfg(test)]
896mod tests {
897 use super::*;
898
899 #[test]
900 fn test_reflection_config_defaults() {
901 let config = ReflectionConfig::default();
902 assert_eq!(config.importance_trigger_threshold, 150.0);
903 assert_eq!(config.target_insights_per_reflection, 3);
904 assert_eq!(config.insight_importance_multiplier, 1.5);
905 }
906
907 #[test]
908 fn test_knowledge_graph_creation() {
909 let mut graph = KnowledgeGraph::new();
910
911 let node = KnowledgeNode {
912 id: Uuid::new_v4(),
913 concept: "test_concept".to_string(),
914 node_type: NodeType::Concept,
915 embedding: None,
916 confidence: 0.8,
917 connections: Vec::new(),
918 created_at: Utc::now(),
919 };
920
921 let node_id = node.id;
922 graph.add_node(node);
923
924 assert!(graph.nodes.contains_key(&node_id));
925 assert!(graph.concept_index.contains_key("test_concept"));
926 }
927
928 #[test]
929 fn test_insight_type_serialization() {
930 let insight_type = InsightType::Pattern;
931 let serialized = serde_json::to_string(&insight_type).unwrap();
932 let deserialized: InsightType = serde_json::from_str(&serialized).unwrap();
933 assert_eq!(insight_type, deserialized);
934 }
935}