use std::collections::HashMap;
use terraphim_rolegraph::RoleGraph;
use terraphim_types::{
Document, DocumentType, LogicalOperator, NormalizedTerm, NormalizedTermValue, RoleName,
Thesaurus,
};
const SEPARATOR: &str = "═══════════════════════════════════════════════════════════════════════";
fn section_header(title: &str) {
println!("\n{}", SEPARATOR);
println!(" {}", title);
println!("{}", SEPARATOR);
}
fn subsection(title: &str) {
println!("\n--- {} ---\n", title);
}
#[derive(Debug, Clone)]
struct RetrievalMetrics {
query: String,
results_before: usize,
results_after: usize,
top_rank_before: u64,
top_rank_after: u64,
improvement: String,
}
impl RetrievalMetrics {
fn summary(&self) -> String {
let rank_improvement = if self.results_before == 0 {
format!("{} new results", self.results_after)
} else if self.top_rank_after > self.top_rank_before {
format!(
"rank improved {} -> {}",
self.top_rank_before, self.top_rank_after
)
} else {
"maintained quality".to_string()
};
format!("Query '{}': {}", self.query, rank_improvement)
}
}
fn build_base_thesaurus() -> Thesaurus {
let mut thesaurus = Thesaurus::new("Domain Expert - Base".to_string());
let base_concepts = vec![
(
"distributed systems",
vec!["distributed computing", "distributed architecture"],
),
(
"machine learning",
vec!["ml", "ai", "artificial intelligence"],
),
("database", vec!["db", "data store", "storage"]),
(
"caching",
vec!["cache", "memoization", "cache invalidation"],
),
("api design", vec!["rest api", "graphql", "api gateway"]),
(
"testing",
vec!["unit test", "integration test", "test coverage"],
),
("performance", vec!["optimization", "latency", "throughput"]),
(
"security",
vec!["authentication", "authorization", "encryption"],
),
];
let mut id = 1u64;
for (concept, synonyms) in base_concepts {
let term = NormalizedTerm::new(id, NormalizedTermValue::new(concept.to_string()))
.with_display_value(concept.to_string());
thesaurus.insert(NormalizedTermValue::new(concept.to_string()), term);
for syn in synonyms {
let syn_term = NormalizedTerm::new(id, NormalizedTermValue::new(concept.to_string()))
.with_display_value(concept.to_string());
thesaurus.insert(NormalizedTermValue::new(syn.to_string()), syn_term);
}
id += 1;
}
thesaurus
}
fn build_enhanced_thesaurus() -> Thesaurus {
let mut thesaurus = build_base_thesaurus();
let mut next_id = 9u64;
let enhanced_concepts = vec![
(
"consensus algorithms",
vec![
"raft",
"paxos",
"byzantine fault tolerance",
"leader election",
],
),
(
"cap theorem",
vec![
"consistency availability partition",
"cap trade-offs",
"brewer theorem",
],
),
(
"event sourcing",
vec!["event store", "cqrs", "event replay", "event log"],
),
(
"sharding",
vec![
"database sharding",
"horizontal partitioning",
"shard key",
"data partitioning",
],
),
(
"circuit breaker",
vec!["fault tolerance", "cascading failure", "resilience pattern"],
),
(
"load balancing",
vec![
"load balancer",
"round robin",
"least connections",
"traffic distribution",
],
),
(
"replication",
vec![
"data replication",
"master slave",
"primary replica",
"replica set",
],
),
(
"indexing",
vec![
"database index",
"b-tree",
"hash index",
"query optimization",
],
),
(
"rate limiting",
vec![
"throttling",
"request quota",
"api rate limit",
"token bucket",
],
),
(
"observability",
vec!["monitoring", "logging", "tracing", "metrics", "alerts"],
),
];
for (concept, synonyms) in enhanced_concepts {
let term = NormalizedTerm::new(next_id, NormalizedTermValue::new(concept.to_string()))
.with_display_value(concept.to_string());
thesaurus.insert(NormalizedTermValue::new(concept.to_string()), term);
for syn in synonyms {
let syn_term =
NormalizedTerm::new(next_id, NormalizedTermValue::new(concept.to_string()))
.with_display_value(concept.to_string());
thesaurus.insert(NormalizedTermValue::new(syn.to_string()), syn_term);
}
next_id += 1;
}
thesaurus
}
fn create_knowledge_documents() -> Vec<Document> {
vec![
Document {
id: "doc_consensus_raft".to_string(),
title: "Understanding Raft Consensus Algorithm".to_string(),
url: "file:///knowledge/consensus/raft.md".to_string(),
body: r#"
The Raft consensus algorithm is designed for understandability. It solves the
distributed consensus problem by electing a leader that manages log replication.
Key components:
- Leader Election: Nodes elect a leader via randomized timeouts
- Log Replication: Leader accepts writes and replicates to followers
- Safety: Only nodes with complete logs can become leaders
Raft is used in production systems like etcd, Consul, and TiKV. The algorithm
guarantees that once a majority of nodes agree on a value, it's committed.
Related concepts: paxos, byzantine fault tolerance, distributed systems.
"#
.to_string(),
description: Some("A practical guide to understanding Raft consensus".to_string()),
rank: None,
tags: Some(vec![
"consensus".to_string(),
"distributed-systems".to_string(),
]),
source_haystack: None,
doc_type: DocumentType::KgEntry,
synonyms: None,
route: None,
priority: None,
summarization: None,
stub: None,
},
Document {
id: "doc_cap_theorem".to_string(),
title: "CAP Theorem: The Fundamental Trade-off".to_string(),
url: "file:///knowledge/distributed/cap-theorem.md".to_string(),
body: r#"
The CAP theorem (Brewer's theorem) states that a distributed system can only
guarantee two of three properties simultaneously:
1. Consistency: All nodes see the same data at the same time
2. Availability: Every request receives a response (success or failure)
3. Partition Tolerance: System continues operating despite network failures
In practice, this means choosing between:
- CP systems (e.g., traditional RDBMS with strong consistency)
- AP systems (e.g., Cassandra, DynamoDB with eventual consistency)
Understanding CAP trade-offs is essential for distributed systems design.
"#
.to_string(),
description: Some("Understanding the CAP theorem in distributed systems".to_string()),
rank: None,
tags: Some(vec![
"distributed-systems".to_string(),
"theory".to_string(),
]),
source_haystack: None,
doc_type: DocumentType::KgEntry,
synonyms: None,
route: None,
priority: None,
summarization: None,
stub: None,
},
Document {
id: "doc_sharding".to_string(),
title: "Database Sharding Strategies".to_string(),
url: "file:///knowledge/database/sharding.md".to_string(),
body: r#"
Database sharding (horizontal partitioning) distributes data across multiple servers.
Common strategies:
- Hash-based sharding: Distribute by hash of shard key
- Range-based sharding: Divide by value ranges
- Directory-based: Lookup service maps keys to shards
Critical decisions:
- Shard key selection impacts data distribution
- Hot spots occur with uneven distribution
- Rebalancing needed as data grows
Sharding enables horizontal scaling but adds complexity to queries and transactions.
Related: replication, clustering, database, partitioning.
"#
.to_string(),
description: Some("Strategies for effective database sharding".to_string()),
rank: None,
tags: Some(vec!["database".to_string(), "scalability".to_string()]),
source_haystack: None,
doc_type: DocumentType::KgEntry,
synonyms: None,
route: None,
priority: None,
summarization: None,
stub: None,
},
Document {
id: "doc_event_sourcing".to_string(),
title: "Event Sourcing and CQRS Pattern".to_string(),
url: "file:///knowledge/architecture/event-sourcing.md".to_string(),
body: r#"
Event sourcing stores all changes as a sequence of events rather than just
the current state. Combined with CQRS (Command Query Responsibility Segregation),
it provides powerful capabilities.
Benefits:
- Complete audit trail of all changes
- Temporal queries (what was the state at time T?)
- Event replay for debugging and recovery
- Natural fit for distributed systems
Implementation:
1. Store events in an append-only log (event store)
2. Rebuild state by replaying events
3. Separate read models from write models (CQRS)
Used in: financial systems, collaboration tools, event-driven architectures.
"#
.to_string(),
description: Some("Implementing event sourcing with CQRS".to_string()),
rank: None,
tags: Some(vec!["architecture".to_string(), "patterns".to_string()]),
source_haystack: None,
doc_type: DocumentType::KgEntry,
synonyms: None,
route: None,
priority: None,
summarization: None,
stub: None,
},
Document {
id: "doc_circuit_breaker".to_string(),
title: "Circuit Breaker Pattern for Resilience".to_string(),
url: "file:///knowledge/resilience/circuit-breaker.md".to_string(),
body: r#"
The circuit breaker pattern prevents cascading failures in distributed systems.
States:
- CLOSED: Requests flow normally
- OPEN: Requests fail fast without calling the service
- HALF-OPEN: Limited requests to test if service recovered
Configuration:
- Failure threshold: Number of failures before opening
- Timeout: Duration before attempting recovery
- Success threshold: Consecutive successes to close
Benefits:
- Fails fast, preserving resources
- Allows downstream services time to recover
- Provides clear failure semantics
Related: rate limiting, fault tolerance, resilience patterns.
"#
.to_string(),
description: Some("Implementing resilient services with circuit breakers".to_string()),
rank: None,
tags: Some(vec!["resilience".to_string(), "patterns".to_string()]),
source_haystack: None,
doc_type: DocumentType::KgEntry,
synonyms: None,
route: None,
priority: None,
summarization: None,
stub: None,
},
Document {
id: "doc_load_balancing".to_string(),
title: "Load Balancing Strategies".to_string(),
url: "file:///knowledge/infrastructure/load-balancing.md".to_string(),
body: r#"
Load balancing distributes incoming traffic across multiple servers.
Algorithms:
- Round Robin: Sequential distribution
- Least Connections: Route to server with fewest active connections
- IP Hash: Consistent routing based on client IP
- Weighted: Distribute based on server capacity
Health checks ensure traffic only goes to healthy servers.
Layer 4 (Transport) vs Layer 7 (Application):
- L4: Load balance based on IP/port (faster)
- L7: Content-based routing (more flexible)
Tools: HAProxy, Nginx, AWS ALB, cloud load balancers.
Related: caching, api gateway, distributed systems.
"#
.to_string(),
description: Some("Load balancing techniques for high availability".to_string()),
rank: None,
tags: Some(vec![
"infrastructure".to_string(),
"scalability".to_string(),
]),
source_haystack: None,
doc_type: DocumentType::KgEntry,
synonyms: None,
route: None,
priority: None,
summarization: None,
stub: None,
},
Document {
id: "doc_observability".to_string(),
title: "Observability: Logs, Metrics, and Traces".to_string(),
url: "file:///knowledge/observability/overview.md".to_string(),
body: r#"
Observability is the ability to understand the internal state of a system
from its external outputs. The three pillars are:
1. Logs: Discrete events with timestamps
- Structured logging for searchability
- Log levels: DEBUG, INFO, WARN, ERROR
2. Metrics: Numerical measurements over time
- Counters, gauges, histograms
- Prometheus, Grafana for visualization
3. Traces: Request flow across services
- Distributed tracing with spans
- Jaeger, Zipkin for trace visualization
Best practices:
- Correlate logs, metrics, and traces with common IDs
- Set up alerts based on SLOs
- Use dashboards for system health overview
Related: monitoring, alerts, performance, distributed systems.
"#
.to_string(),
description: Some(
"Building observable systems with logs, metrics, and traces".to_string(),
),
rank: None,
tags: Some(vec!["observability".to_string(), "monitoring".to_string()]),
source_haystack: None,
doc_type: DocumentType::KgEntry,
synonyms: None,
route: None,
priority: None,
summarization: None,
stub: None,
},
Document {
id: "doc_indexing".to_string(),
title: "Database Indexing Strategies".to_string(),
url: "file:///knowledge/database/indexing.md".to_string(),
body: r#"
Database indexes accelerate query performance by providing fast data lookup.
Index types:
- B-tree: Balanced tree structure, good for range queries
- Hash: O(1) equality lookups, no range support
- GiST: Generalized search tree for complex types
- Full-text: Inverted indexes for text search
Trade-offs:
- Faster reads, slower writes (index maintenance)
- Storage overhead for index structures
- Must choose indexes based on query patterns
Query optimization:
- Use EXPLAIN to analyze query plans
- Covering indexes avoid table lookups
- Composite indexes for multi-column queries
Related: database, performance, query optimization, sharding.
"#
.to_string(),
description: Some("Optimizing database performance with proper indexing".to_string()),
rank: None,
tags: Some(vec!["database".to_string(), "performance".to_string()]),
source_haystack: None,
doc_type: DocumentType::KgEntry,
synonyms: None,
route: None,
priority: None,
summarization: None,
stub: None,
},
]
}
fn print_thesaurus_info(name: &str, thesaurus: &Thesaurus) {
println!("Thesaurus: {}", name);
println!(" Total terms: {}", thesaurus.len());
let mut concept_count: HashMap<u64, Vec<String>> = HashMap::new();
for (key, term) in thesaurus {
concept_count
.entry(term.id)
.or_default()
.push(key.to_string());
}
println!(" Unique concepts: {}", concept_count.len());
println!("\n Sample concepts:");
for (id, terms) in concept_count.iter().take(5) {
let empty_string = String::new();
let display_term = terms.first().unwrap_or(&empty_string);
println!(
" ID {}: {} ({} synonyms)",
id,
display_term,
terms.len() - 1
);
}
}
fn print_graph_stats(rolegraph: &RoleGraph) {
let stats = rolegraph.get_graph_stats();
println!("Graph Statistics:");
println!(" Nodes (concepts): {}", stats.node_count);
println!(" Edges (connections): {}", stats.edge_count);
println!(" Documents indexed: {}", stats.document_count);
println!(" Thesaurus terms: {}", stats.thesaurus_size);
println!(" Is populated: {}", stats.is_populated);
}
fn print_node_details(rolegraph: &RoleGraph, limit: usize) {
println!("Top nodes by rank:");
let mut nodes: Vec<_> = rolegraph.nodes_map().iter().collect();
nodes.sort_by_key(|(_, n)| std::cmp::Reverse(n.rank));
for (id, node) in nodes.iter().take(limit) {
if let Some(term) = rolegraph.ac_reverse_nterm.get(id) {
println!(
" '{}': rank={}, edges={}",
term,
node.rank,
node.connected_with.len()
);
}
}
}
fn print_query_results(label: &str, results: &[(String, terraphim_types::IndexedDocument)]) {
println!("{}", label);
if results.is_empty() {
println!(" No results found");
return;
}
for (i, (doc_id, indexed)) in results.iter().enumerate() {
println!(
" {}. {} (rank: {}, tags: [{}])",
i + 1,
doc_id,
indexed.rank,
indexed.tags.join(", ")
);
}
}
fn compare_retrieval(
query: &str,
initial_results: &[(String, terraphim_types::IndexedDocument)],
enhanced_results: &[(String, terraphim_types::IndexedDocument)],
) -> RetrievalMetrics {
let (results_before, rank_before) = if initial_results.is_empty() {
(0, 0)
} else {
(initial_results.len(), initial_results[0].1.rank)
};
let (results_after, rank_after) = if enhanced_results.is_empty() {
(0, 0)
} else {
(enhanced_results.len(), enhanced_results[0].1.rank)
};
let improvement = if results_before == 0 && results_after > 0 {
format!("Discovered {} new results", results_after)
} else if rank_after > rank_before {
format!("Rank improved {} -> {}", rank_before, rank_after)
} else if results_after > results_before {
format!(
"Found {} additional results",
results_after - results_before
)
} else {
"Maintained quality".to_string()
};
RetrievalMetrics {
query: query.to_string(),
results_before,
results_after,
top_rank_before: rank_before,
top_rank_after: rank_after,
improvement,
}
}
async fn run_demonstration() -> Result<(), Box<dyn std::error::Error>> {
section_header("Terraphim Graph Embeddings & Ranking Demonstration");
println!("\nThis demonstration shows how Terraphim's knowledge graph-based");
println!("retrieval system improves document search through graph embeddings.");
println!("\nGenerated by GLM-5 (zai-coding-plan/glm-5)");
section_header("Part 1: Building the Knowledge Graph");
subsection("Creating Base Thesaurus");
let base_thesaurus = build_base_thesaurus();
print_thesaurus_info("Base Domain Knowledge", &base_thesaurus);
subsection("Creating Enhanced Thesaurus");
let enhanced_thesaurus = build_enhanced_thesaurus();
print_thesaurus_info("Enhanced Domain Knowledge", &enhanced_thesaurus);
println!(
"\n -> Added {} new domain-specific terms",
enhanced_thesaurus.len() - base_thesaurus.len()
);
section_header("Part 2: Creating the Agent Role with RoleGraph");
let role_name = RoleName::new("Domain Expert");
subsection("Building RoleGraphs");
println!("Creating RoleGraph for: {}\n", role_name);
let mut base_rolegraph = RoleGraph::new(role_name.clone(), base_thesaurus).await?;
let mut enhanced_rolegraph = RoleGraph::new(role_name.clone(), enhanced_thesaurus).await?;
subsection("Indexing Knowledge Documents");
let documents = create_knowledge_documents();
println!("Indexing {} documents...\n", documents.len());
for doc in &documents {
base_rolegraph.insert_document(&doc.id, doc.clone());
enhanced_rolegraph.insert_document(&doc.id, doc.clone());
println!(" Indexed: {}", doc.title);
}
subsection("Base RoleGraph Statistics");
print_graph_stats(&base_rolegraph);
subsection("Enhanced RoleGraph Statistics");
print_graph_stats(&enhanced_rolegraph);
section_header("Part 3: Graph Embedding Visualization");
subsection("Base RoleGraph - Top Nodes");
print_node_details(&base_rolegraph, 5);
subsection("Enhanced RoleGraph - Top Nodes");
print_node_details(&enhanced_rolegraph, 8);
section_header("Part 4: Retrieval Comparison");
let test_queries = vec![
("raft consensus", "Tests specific algorithm knowledge"),
("cap theorem trade-offs", "Tests theoretical concepts"),
(
"database sharding partitioning",
"Tests database scalability",
),
("event sourcing cqrs", "Tests architecture patterns"),
("circuit breaker resilience", "Tests reliability patterns"),
("load balancing distribution", "Tests infrastructure"),
("observability monitoring", "Tests operations"),
("distributed systems consistency", "Tests core concepts"),
];
let mut all_metrics = Vec::new();
for (query, description) in &test_queries {
subsection(&format!("Query: \"{}\"", query));
println!("Purpose: {}\n", description);
let base_results = base_rolegraph.query_graph(query, Some(0), Some(5))?;
let enhanced_results = enhanced_rolegraph.query_graph(query, Some(0), Some(5))?;
print_query_results("Base Thesaurus Results:", &base_results);
println!();
print_query_results("Enhanced Thesaurus Results:", &enhanced_results);
let metrics = compare_retrieval(query, &base_results, &enhanced_results);
println!("\n Improvement: {}", metrics.improvement);
all_metrics.push(metrics);
}
section_header("Part 5: Multi-Term Queries with AND/OR Operators");
subsection("AND Query (must match all terms)");
let and_query_terms = vec!["distributed systems", "consensus"];
println!("Query terms: {:?}\n", and_query_terms);
let and_results = enhanced_rolegraph.query_graph_with_operators(
&and_query_terms,
&LogicalOperator::And,
Some(0),
Some(5),
)?;
print_query_results("AND Results:", &and_results);
subsection("OR Query (match any term)");
println!("Query terms: {:?}\n", and_query_terms);
let or_results = enhanced_rolegraph.query_graph_with_operators(
&and_query_terms,
&LogicalOperator::Or,
Some(0),
Some(5),
)?;
print_query_results("OR Results:", &or_results);
section_header("Part 6: Connectivity Analysis");
let connectivity_queries = vec![
"raft leader election log replication",
"cap theorem consistency availability",
"event sourcing event store cqrs",
];
for query in connectivity_queries {
let is_connected = enhanced_rolegraph.is_all_terms_connected_by_path(query);
let matched = enhanced_rolegraph.find_matching_node_ids(query);
println!("\nQuery: \"{}\"", query);
println!(" Matched terms: {}", matched.len());
println!(
" All connected: {}",
if is_connected {
"YES - high semantic coherence"
} else {
"NO - terms not all connected"
}
);
}
section_header("Part 7: Summary of Improvements");
println!("\nRetrieval improvements from adding domain-specific terms:\n");
for metrics in &all_metrics {
println!(" - {}", metrics.summary());
}
section_header("Key Learnings");
println!("\n1. GRAPH EMBEDDINGS");
println!(" - Built from term co-occurrence in documents");
println!(" - Each document creates edges between its terms");
println!(" - Node rank = document frequency");
println!(" - Edge rank = co-occurrence frequency");
println!("\n2. RANKING FORMULA");
println!(" total_rank = node.rank + edge.rank + document_rank");
println!(" - Surfaces documents with highly-connected terms");
println!(" - Aggregates rank from multiple matching paths");
println!("\n3. KNOWLEDGE GRAPH IMPROVEMENT");
println!(" - Base thesaurus: generic terms only");
println!(" - Enhanced thesaurus: domain-specific terms with synonyms");
println!(" - Result: Better retrieval for domain queries");
println!("\n4. MULTI-TERM QUERIES");
println!(" - AND: Requires all terms (higher precision)");
println!(" - OR: Any term matches (higher recall)");
println!(" - Connectivity: Indicates semantic coherence");
println!("\n{}", SEPARATOR);
println!(" Demonstration Complete");
println!(" Generated by GLM-5 (zai-coding-plan/glm-5)");
println!("{}", SEPARATOR);
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
run_demonstration().await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_base_thesaurus_creation() {
let thesaurus = build_base_thesaurus();
assert!(thesaurus.len() > 0);
let distributed =
thesaurus.get(&NormalizedTermValue::new("distributed systems".to_string()));
assert!(distributed.is_some());
let synonym = thesaurus.get(&NormalizedTermValue::new(
"distributed computing".to_string(),
));
assert!(synonym.is_some());
assert_eq!(distributed.unwrap().id, synonym.unwrap().id);
}
#[tokio::test]
async fn test_enhanced_has_more_terms() {
let base = build_base_thesaurus();
let enhanced = build_enhanced_thesaurus();
assert!(enhanced.len() > base.len());
let raft = enhanced.get(&NormalizedTermValue::new("raft".to_string()));
assert!(raft.is_some());
let cap = enhanced.get(&NormalizedTermValue::new("cap theorem".to_string()));
assert!(cap.is_some());
}
#[tokio::test]
async fn test_rolegraph_creation() {
let thesaurus = build_enhanced_thesaurus();
let role = RoleName::new("Test Role");
let rolegraph = RoleGraph::new(role, thesaurus).await.unwrap();
assert!(rolegraph.get_node_count() == 0);
assert!(rolegraph.get_edge_count() == 0);
}
#[tokio::test]
async fn test_document_indexing() {
let thesaurus = build_enhanced_thesaurus();
let role = RoleName::new("Test Role");
let mut rolegraph = RoleGraph::new(role, thesaurus).await.unwrap();
let docs = create_knowledge_documents();
for doc in &docs {
rolegraph.insert_document(&doc.id, doc.clone());
}
assert!(rolegraph.get_document_count() > 0);
assert!(rolegraph.get_node_count() > 0);
assert!(rolegraph.get_edge_count() > 0);
}
#[tokio::test]
async fn test_query_with_enhanced_terms() {
let thesaurus = build_enhanced_thesaurus();
let role = RoleName::new("Test Role");
let mut rolegraph = RoleGraph::new(role, thesaurus).await.unwrap();
let docs = create_knowledge_documents();
for doc in &docs {
rolegraph.insert_document(&doc.id, doc.clone());
}
let results = rolegraph.query_graph("raft consensus", None, None).unwrap();
assert!(
!results.is_empty(),
"Should find results for 'raft consensus'"
);
let cap_results = rolegraph.query_graph("cap theorem", None, None).unwrap();
assert!(
!cap_results.is_empty(),
"Should find results for 'cap theorem'"
);
}
#[tokio::test]
async fn test_and_or_operators() {
let thesaurus = build_enhanced_thesaurus();
let role = RoleName::new("Test Role");
let mut rolegraph = RoleGraph::new(role, thesaurus).await.unwrap();
let docs = create_knowledge_documents();
for doc in &docs {
rolegraph.insert_document(&doc.id, doc.clone());
}
let terms = vec!["distributed systems", "database"];
let and_results = rolegraph
.query_graph_with_operators(&terms, &LogicalOperator::And, None, None)
.unwrap();
let or_results = rolegraph
.query_graph_with_operators(&terms, &LogicalOperator::Or, None, None)
.unwrap();
assert!(
or_results.len() >= and_results.len(),
"OR should have >= results than AND"
);
}
#[tokio::test]
async fn test_ranking_formula() {
let thesaurus = build_enhanced_thesaurus();
let role = RoleName::new("Test Role");
let mut rolegraph = RoleGraph::new(role, thesaurus).await.unwrap();
let docs = create_knowledge_documents();
for doc in &docs {
rolegraph.insert_document(&doc.id, doc.clone());
}
let results = rolegraph
.query_graph("raft consensus algorithms", None, None)
.unwrap();
if !results.is_empty() {
let (_, first_result) = &results[0];
assert!(first_result.rank > 0, "Rank should be positive");
assert!(!first_result.tags.is_empty(), "Should have matched tags");
assert!(
!first_result.matched_edges.is_empty(),
"Should have matched edges"
);
}
}
}