use crate::trajectory::graph::NodeId;
use super::manager::ChainId;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LinkStrength {
pub semantic: f32,
pub temporal: f32,
pub thematic: f32,
pub combined: f32,
}
impl LinkStrength {
pub fn new(semantic: f32, temporal: f32, thematic: f32) -> Self {
let combined = 0.5 * semantic + 0.2 * temporal + 0.3 * thematic;
Self {
semantic,
temporal,
thematic,
combined,
}
}
pub fn from_semantic(similarity: f32) -> Self {
Self::new(similarity, 0.5, 0.5)
}
pub fn is_strong(&self, threshold: f32) -> bool {
self.combined >= threshold
}
}
impl Default for LinkStrength {
fn default() -> Self {
Self {
semantic: 0.0,
temporal: 0.0,
thematic: 0.0,
combined: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct CrossChainLink {
pub source_chain: ChainId,
pub source_node: NodeId,
pub target_chain: ChainId,
pub target_node: NodeId,
pub strength: LinkStrength,
pub link_type: CrossChainLinkType,
pub description: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CrossChainLinkType {
Semantic,
Reference,
Continuation,
Related,
Alternative,
KnowledgeTransfer,
}
impl CrossChainLink {
pub fn new(
source_chain: ChainId,
source_node: NodeId,
target_chain: ChainId,
target_node: NodeId,
strength: LinkStrength,
link_type: CrossChainLinkType,
) -> Self {
Self {
source_chain,
source_node,
target_chain,
target_node,
strength,
link_type,
description: None,
}
}
pub fn semantic(
source_chain: ChainId,
source_node: NodeId,
target_chain: ChainId,
target_node: NodeId,
similarity: f32,
) -> Self {
Self::new(
source_chain,
source_node,
target_chain,
target_node,
LinkStrength::from_semantic(similarity),
CrossChainLinkType::Semantic,
)
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn is_bidirectional(&self) -> bool {
matches!(
self.link_type,
CrossChainLinkType::Semantic | CrossChainLinkType::Related
)
}
pub fn reverse(&self) -> Self {
Self {
source_chain: self.target_chain.clone(),
source_node: self.target_node,
target_chain: self.source_chain.clone(),
target_node: self.source_node,
strength: self.strength,
link_type: self.link_type,
description: self.description.clone(),
}
}
}
pub fn find_cross_chain_links(
_chain_embeddings: &[(ChainId, Vec<(NodeId, Vec<f32>)>)],
_threshold: f32,
) -> Vec<CrossChainLink> {
Vec::new()
}
pub const KNOWLEDGE_TRANSFER_PATTERNS: &[&str] = &[
"as we discussed",
"like we did before",
"similar to what we",
"remember when we",
"building on the",
"like in the",
"same as before",
"just like last time",
"from our previous",
"we already covered",
];
pub fn detect_knowledge_transfer(text: &str) -> Option<&'static str> {
let lower = text.to_lowercase();
for pattern in KNOWLEDGE_TRANSFER_PATTERNS {
if lower.contains(pattern) {
return Some(pattern);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_link_strength() {
let strength = LinkStrength::new(0.8, 0.5, 0.6);
assert!(strength.combined > 0.0);
assert!(strength.is_strong(0.5));
assert!(!strength.is_strong(0.9));
}
#[test]
fn test_cross_chain_link() {
let link = CrossChainLink::semantic(
"chain1".to_string(),
1,
"chain2".to_string(),
2,
0.85,
);
assert_eq!(link.source_chain, "chain1");
assert_eq!(link.target_chain, "chain2");
assert!(link.is_bidirectional());
let reverse = link.reverse();
assert_eq!(reverse.source_chain, "chain2");
assert_eq!(reverse.target_chain, "chain1");
}
#[test]
fn test_knowledge_transfer_detection() {
assert!(detect_knowledge_transfer("As we discussed before, this should work").is_some());
assert!(detect_knowledge_transfer("Similar to what we did in the auth project").is_some());
assert!(detect_knowledge_transfer("This is completely new").is_none());
}
#[test]
fn test_link_with_description() {
let link = CrossChainLink::semantic(
"chain1".to_string(),
1,
"chain2".to_string(),
2,
0.85,
).with_description("Both discuss authentication");
assert_eq!(link.description, Some("Both discuss authentication".to_string()));
}
}