reputation-core 0.1.0

Core calculation engine for the KnowThat Reputation System with advanced scoring algorithms
Documentation
//! Integration patterns for the reputation engine
//! 
//! This example demonstrates how to integrate the reputation engine
//! into various application architectures.

use reputation_core::Calculator;
use reputation_types::{AgentData, AgentDataBuilder, ReputationScore};
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Reputation Engine Integration Patterns ===\n");
    
    // 1. Basic caching pattern
    println!("1. CACHING PATTERN");
    println!("------------------");
    caching_pattern()?;
    
    // 2. Concurrent access pattern
    println!("\n2. CONCURRENT ACCESS PATTERN");
    println!("----------------------------");
    concurrent_pattern()?;
    
    // 3. Event-driven pattern
    println!("\n3. EVENT-DRIVEN PATTERN");
    println!("-----------------------");
    event_driven_pattern()?;
    
    // 4. Service layer pattern
    println!("\n4. SERVICE LAYER PATTERN");
    println!("------------------------");
    service_layer_pattern()?;
    
    // 5. Rate limiting pattern
    println!("\n5. RATE LIMITING PATTERN");
    println!("------------------------");
    rate_limiting_pattern()?;
    
    Ok(())
}

// Simple in-memory cache
struct ScoreCache {
    cache: RwLock<HashMap<String, (ReputationScore, Instant)>>,
    ttl: Duration,
}

impl ScoreCache {
    fn new(ttl: Duration) -> Self {
        Self {
            cache: RwLock::new(HashMap::new()),
            ttl,
        }
    }
    
    fn get(&self, did: &str) -> Option<ReputationScore> {
        let cache = self.cache.read().unwrap();
        if let Some((score, timestamp)) = cache.get(did) {
            if timestamp.elapsed() < self.ttl {
                return Some(score.clone());
            }
        }
        None
    }
    
    fn set(&self, did: String, score: ReputationScore) {
        let mut cache = self.cache.write().unwrap();
        cache.insert(did, (score, Instant::now()));
    }
    
    fn invalidate(&self, did: &str) {
        let mut cache = self.cache.write().unwrap();
        cache.remove(did);
    }
}

fn caching_pattern() -> Result<(), Box<dyn std::error::Error>> {
    let calculator = Calculator::default();
    let cache = ScoreCache::new(Duration::from_secs(300)); // 5 minute TTL
    
    // Function to get score with caching
    let calculate_with_cache = |agent: &AgentData| -> Result<ReputationScore, Box<dyn std::error::Error>> {
        // Check cache first
        if let Some(cached_score) = cache.get(&agent.did) {
            println!("  Cache HIT for {}", agent.did);
            return Ok(cached_score);
        }
        
        // Calculate if not cached
        println!("  Cache MISS for {} - calculating...", agent.did);
        let score = calculator.calculate(agent)?;
        
        // Store in cache
        cache.set(agent.did.clone(), score.clone());
        
        Ok(score)
    };
    
    // Test the cache
    let agent = AgentDataBuilder::new("did:example:cached")
        .with_reviews(100, 4.2)
        .total_interactions(150)
        .build()?;
    
    // First call - cache miss
    let score1 = calculate_with_cache(&agent)?;
    println!("  Score: {:.1}", score1.score);
    
    // Second call - cache hit
    let score2 = calculate_with_cache(&agent)?;
    assert_eq!(score1.score, score2.score);
    
    // Invalidate cache when data changes
    println!("  Invalidating cache...");
    cache.invalidate(&agent.did);
    
    // Next call - cache miss again
    let _score3 = calculate_with_cache(&agent)?;
    
    Ok(())
}

fn concurrent_pattern() -> Result<(), Box<dyn std::error::Error>> {
    let calculator = Arc::new(Calculator::default());
    let results = Arc::new(Mutex::new(Vec::new()));
    
    // Generate test agents
    let agents: Vec<_> = (0..20)
        .map(|i| {
            AgentDataBuilder::new(&format!("did:example:concurrent{}", i))
                .with_reviews(i * 10, 3.5 + (i as f64 * 0.1))
                .total_interactions(i * 15)
                .build()
                .unwrap()
        })
        .collect();
    
    // Process agents concurrently
    let mut handles = vec![];
    
    for agent in agents {
        let calc = Arc::clone(&calculator);
        let res = Arc::clone(&results);
        
        let handle = std::thread::spawn(move || {
            // Each thread can safely use the shared calculator
            match calc.calculate(&agent) {
                Ok(score) => {
                    println!("  Thread {:?} calculated score for {}: {:.1}", 
                        std::thread::current().id(), 
                        agent.did, 
                        score.score
                    );
                    res.lock().unwrap().push((agent.did, score));
                }
                Err(e) => eprintln!("  Error: {}", e),
            }
        });
        
        handles.push(handle);
    }
    
    // Wait for all threads
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("  Processed {} agents concurrently", results.lock().unwrap().len());
    
    Ok(())
}

// Event types for event-driven pattern
enum ReputationEvent {
    ReviewAdded { agent_id: String, rating: f64 },
    AgentUpdated { agent_id: String },
    BatchProcess { agent_ids: Vec<String> },
}

struct EventProcessor {
    #[allow(dead_code)]
    calculator: Calculator,
    cache: ScoreCache,
}

impl EventProcessor {
    fn new() -> Self {
        Self {
            calculator: Calculator::default(),
            cache: ScoreCache::new(Duration::from_secs(600)),
        }
    }
    
    fn process_event(&self, event: ReputationEvent) -> Result<(), Box<dyn std::error::Error>> {
        match event {
            ReputationEvent::ReviewAdded { agent_id, rating } => {
                println!("  Processing ReviewAdded: {} got {:.1}★", agent_id, rating);
                // Invalidate cache for this agent
                self.cache.invalidate(&agent_id);
                // In real app: recalculate score
            }
            
            ReputationEvent::AgentUpdated { agent_id } => {
                println!("  Processing AgentUpdated: {}", agent_id);
                self.cache.invalidate(&agent_id);
            }
            
            ReputationEvent::BatchProcess { agent_ids } => {
                println!("  Processing BatchProcess for {} agents", agent_ids.len());
                // In real app: load agents and process in batch
                for id in &agent_ids {
                    self.cache.invalidate(id);
                }
            }
        }
        Ok(())
    }
}

fn event_driven_pattern() -> Result<(), Box<dyn std::error::Error>> {
    let processor = EventProcessor::new();
    
    // Simulate events
    let events = vec![
        ReputationEvent::ReviewAdded {
            agent_id: "did:example:event1".to_string(),
            rating: 4.5,
        },
        ReputationEvent::AgentUpdated {
            agent_id: "did:example:event2".to_string(),
        },
        ReputationEvent::BatchProcess {
            agent_ids: vec![
                "did:example:batch1".to_string(),
                "did:example:batch2".to_string(),
                "did:example:batch3".to_string(),
            ],
        },
    ];
    
    for event in events {
        processor.process_event(event)?;
    }
    
    Ok(())
}

// Service layer for reputation calculations
struct ReputationService {
    calculator: Arc<Calculator>,
    cache: Arc<ScoreCache>,
}

impl ReputationService {
    fn new() -> Self {
        Self {
            calculator: Arc::new(Calculator::default()),
            cache: Arc::new(ScoreCache::new(Duration::from_secs(300))),
        }
    }
    
    fn get_score(&self, agent: &AgentData) -> Result<ReputationScore, Box<dyn std::error::Error>> {
        // Check cache
        if let Some(score) = self.cache.get(&agent.did) {
            return Ok(score);
        }
        
        // Calculate
        let score = self.calculator.calculate(agent)?;
        self.cache.set(agent.did.clone(), score.clone());
        
        Ok(score)
    }
    
    #[allow(dead_code)]
    fn get_scores_batch(&self, agents: &[AgentData]) -> Vec<Result<ReputationScore, reputation_core::ReputationError>> {
        // Use batch processing for efficiency
        self.calculator.calculate_batch(agents)
    }
    
    fn get_score_explanation(&self, agent: &AgentData) -> Result<String, Box<dyn std::error::Error>> {
        let explanation = self.calculator.explain_score(agent)?;
        Ok(explanation.explanation)
    }
    
    fn predict_score_change(&self, agent: &AgentData, new_reviews: u32, rating: f64) 
        -> Result<reputation_core::ScorePrediction, Box<dyn std::error::Error>> {
        self.calculator.predict_score_change(agent, new_reviews, rating)
            .map_err(|e| e.into())
    }
}

fn service_layer_pattern() -> Result<(), Box<dyn std::error::Error>> {
    let service = ReputationService::new();
    
    let agent = AgentDataBuilder::new("did:example:service")
        .with_reviews(75, 4.3)
        .total_interactions(100)
        .mcp_level(2)
        .build()?;
    
    // Use service methods
    println!("  Getting score...");
    let score = service.get_score(&agent)?;
    println!("  Score: {:.1}", score.score);
    
    println!("\n  Getting explanation...");
    let explanation = service.get_score_explanation(&agent)?;
    println!("{}", explanation.split('\n').take(3).collect::<Vec<_>>().join("\n"));
    
    println!("\n  Predicting score change...");
    let prediction = service.predict_score_change(&agent, 25, 4.5)?;
    println!("  Change: {:+.1} points", prediction.score_change);
    
    Ok(())
}

// Rate limiter for API protection
struct RateLimiter {
    requests: Mutex<HashMap<String, Vec<Instant>>>,
    max_requests: usize,
    window: Duration,
}

impl RateLimiter {
    fn new(max_requests: usize, window: Duration) -> Self {
        Self {
            requests: Mutex::new(HashMap::new()),
            max_requests,
            window,
        }
    }
    
    fn check_rate_limit(&self, client_id: &str) -> bool {
        let mut requests = self.requests.lock().unwrap();
        let now = Instant::now();
        
        let client_requests = requests.entry(client_id.to_string()).or_insert_with(Vec::new);
        
        // Remove old requests outside the window
        client_requests.retain(|&req_time| now.duration_since(req_time) < self.window);
        
        if client_requests.len() < self.max_requests {
            client_requests.push(now);
            true
        } else {
            false
        }
    }
}

fn rate_limiting_pattern() -> Result<(), Box<dyn std::error::Error>> {
    let calculator = Calculator::default();
    let rate_limiter = RateLimiter::new(5, Duration::from_secs(60)); // 5 requests per minute
    
    let agent = AgentDataBuilder::new("did:example:ratelimited")
        .with_reviews(50, 4.0)
        .total_interactions(75)
        .build()?;
    
    // Simulate API requests from a client
    let client_id = "client_123";
    
    for i in 0..8 {
        if rate_limiter.check_rate_limit(client_id) {
            let score = calculator.calculate(&agent)?;
            println!("  Request {} - OK: score = {:.1}", i + 1, score.score);
        } else {
            println!("  Request {} - RATE LIMITED", i + 1);
        }
        
        // Small delay to show rate limiting
        std::thread::sleep(Duration::from_millis(100));
    }
    
    Ok(())
}