reputation-core 0.1.0

Core calculation engine for the KnowThat Reputation System with advanced scoring algorithms
Documentation
//! Integration tests combining multiple Phase 2 features

use reputation_core::{
    Calculator, CalculatorPreset, BonusConfig,
    BatchOptions, ConfidenceLevel
};
use reputation_types::AgentDataBuilder;
use std::sync::{Arc, Mutex};

#[test]
fn test_builder_with_batch_processing() {
    // Use builder to create custom calculator
    let calc = Calculator::builder()
        .preset(CalculatorPreset::Conservative)
        .prior_base(55.0)
        .build()
        .unwrap();
    
    // Create agents
    let agents: Vec<_> = (0..100)
        .map(|i| {
            let mut builder = AgentDataBuilder::new(&format!("did:test:agent{}", i))
                .total_interactions(i * 3)
                .mcp_level((i % 4) as u8)
                .identity_verified(i % 2 == 0);
            
            if i > 0 {  // Only add reviews if i > 0
                builder = builder.with_reviews(i * 2, 3.0 + (i as f64 * 0.01));
            }
            
            builder.build().unwrap()
        })
        .collect();
    
    // Process in batch
    let results = calc.calculate_batch(&agents);
    
    // Verify all succeeded
    assert_eq!(results.len(), 100);
    for (i, result) in results.iter().enumerate() {
        let score = result.as_ref().unwrap();
        
        // Conservative preset should show lower confidence
        let expected_conf = (i * 3) as f64 / ((i * 3) as f64 + 30.0);
        assert!((score.confidence - expected_conf).abs() < 0.001);
        
        // Prior should be at least 55 (custom base)
        assert!(score.components.prior_score >= 55.0);
    }
}

#[test]
fn test_utility_methods_with_enhanced_scores() {
    let calc = Calculator::default();
    
    // Create agent with specific characteristics
    let agent = AgentDataBuilder::new("did:test:utility")
        .with_reviews(15, 4.5)
        .total_interactions(20)
        .mcp_level(2)
        .identity_verified(true)
        .build()
        .unwrap();
    
    // Get explanation
    let explanation = calc.explain_score(&agent).unwrap();
    
    // Verify explanation includes component details
    assert!(explanation.explanation.contains("Prior score: 65.0"));
    assert!(explanation.explanation.contains("MCP bonus: +10.0"));
    assert!(explanation.explanation.contains("Identity verified: +5.0"));
    assert!(explanation.explanation.contains("Confidence Level: 57.1% (Medium)"));
    
    // Verify breakdown matches
    assert_eq!(explanation.breakdown.prior_score, 65.0);
    assert_eq!(explanation.breakdown.prior_breakdown.mcp_bonus, 10.0);
    assert_eq!(explanation.breakdown.confidence_level, ConfidenceLevel::Medium);
    
    // Test prediction
    let prediction = calc.predict_score_change(&agent, 30, 5.0).unwrap();
    assert!(prediction.score_change > 0.0);
    assert!(prediction.confidence_change > 0.0);
    
    // Future confidence should be High
    let future_confidence = calc.confidence_after_interactions(20, 30);
    assert!(future_confidence > 0.7);
}

#[test]
fn test_batch_with_progress_tracking_and_components() {
    let progress = Arc::new(Mutex::new(Vec::new()));
    let progress_clone = Arc::clone(&progress);
    
    let options = BatchOptions {
        chunk_size: Some(10),
        fail_fast: false,
        progress_callback: Some(Box::new(move |completed, total| {
            progress_clone.lock().unwrap().push((completed, total));
        })),
    };
    
    let agents: Vec<_> = (0..50)
        .map(|i| {
            AgentDataBuilder::new(&format!("did:test:batch{}", i))
                .with_reviews(10 + i, 3.5 + (i as f64 * 0.02))
                .total_interactions(15 + i)
                .mcp_level(if i > 25 { 1 } else { 0 })
                .build()
                .unwrap()
        })
        .collect();
    
    let calc = Calculator::default();
    let result = calc.calculate_batch_with_options(&agents, options);
    
    // Check results
    assert_eq!(result.successful_count, 50);
    assert_eq!(result.failed_count, 0);
    
    // Verify components in results
    for (i, calc) in result.calculations.iter().enumerate() {
        let score = calc.result.as_ref().unwrap();
        
        // Later agents should have MCP bonus
        if i > 25 {
            assert_eq!(score.components.prior_breakdown.mcp_bonus, 5.0);
        } else {
            assert_eq!(score.components.prior_breakdown.mcp_bonus, 0.0);
        }
        
        // All should have data points
        assert!(score.data_points > 0);
    }
    
    // Verify progress was tracked
    let progress_log = progress.lock().unwrap();
    assert!(!progress_log.is_empty());
    assert!(progress_log.contains(&(50, 50)));
}

#[test]
fn test_compare_agents_with_different_builders() {
    // Conservative calculator
    let conservative = Calculator::builder()
        .preset(CalculatorPreset::Conservative)
        .build()
        .unwrap();
    
    // Aggressive calculator  
    let aggressive = Calculator::builder()
        .preset(CalculatorPreset::Aggressive)
        .build()
        .unwrap();
    
    // Create two agents
    let agent_a = AgentDataBuilder::new("did:test:alice")
        .with_reviews(30, 4.2)
        .total_interactions(40)
        .mcp_level(1)
        .build()
        .unwrap();
    
    let agent_b = AgentDataBuilder::new("did:test:bob")
        .with_reviews(30, 4.2)
        .total_interactions(40)
        .mcp_level(2)
        .build()
        .unwrap();
    
    // Compare with conservative calculator
    let cons_comparison = conservative.compare_agents(&agent_a, &agent_b).unwrap();
    
    // Compare with aggressive calculator
    let aggr_comparison = aggressive.compare_agents(&agent_a, &agent_b).unwrap();
    
    // Both should identify Bob as higher score (due to MCP level)
    assert_eq!(cons_comparison.higher_score_agent, "did:test:bob");
    assert_eq!(aggr_comparison.higher_score_agent, "did:test:bob");
    
    // But confidence levels will differ
    assert!(cons_comparison.confidence_a < aggr_comparison.confidence_a);
    assert!(cons_comparison.confidence_b < aggr_comparison.confidence_b);
}

#[test]
fn test_provisional_scores_in_batch() {
    let calc = Calculator::default();
    
    // Mix of provisional and established agents
    let agents = vec![
        // Provisional (low confidence)
        AgentDataBuilder::new("did:test:new1")
            .with_reviews(1, 5.0)
            .total_interactions(1)
            .build()
            .unwrap(),
        
        // Provisional (no reviews)
        AgentDataBuilder::new("did:test:new2")
            .build()
            .unwrap(),
        
        // Established (medium confidence)
        AgentDataBuilder::new("did:test:established1")
            .with_reviews(10, 4.0)
            .total_interactions(15)
            .build()
            .unwrap(),
        
        // Established (high confidence)
        AgentDataBuilder::new("did:test:established2")
            .with_reviews(100, 3.5)
            .total_interactions(150)
            .build()
            .unwrap(),
    ];
    
    let results = calc.calculate_batch(&agents);
    
    // Check provisional flags
    assert!(results[0].as_ref().unwrap().is_provisional);
    assert!(results[1].as_ref().unwrap().is_provisional);
    assert!(!results[2].as_ref().unwrap().is_provisional);
    assert!(!results[3].as_ref().unwrap().is_provisional);
    
    // Check confidence levels
    assert_eq!(results[0].as_ref().unwrap().level, ConfidenceLevel::Low);
    assert_eq!(results[1].as_ref().unwrap().level, ConfidenceLevel::Low);
    assert_eq!(results[2].as_ref().unwrap().level, ConfidenceLevel::Medium);
    assert_eq!(results[3].as_ref().unwrap().level, ConfidenceLevel::High);
}

#[test]
fn test_builder_bonus_config_integration() {
    // Create custom bonus configuration
    let bonuses = BonusConfig {
        mcp_bonus_per_level: 3.0,  // 3.0 per level instead of default 5.0
        identity_bonus: 2.0,
        security_audit_bonus: 4.0,
        open_source_bonus: 1.0,
        age_bonus: 3.0,
    };
    
    let calc = Calculator::builder()
        .prior_bonuses(bonuses)
        .build()
        .unwrap();
    
    // Test agent with all bonuses
    let agent = AgentDataBuilder::new("did:test:bonuses")
        .with_reviews(50, 4.0)
        .total_interactions(60)
        .mcp_level(3)
        .identity_verified(true)
        .security_audit_passed(true)
        .open_source(true)
        .build()
        .unwrap();
    
    let score = calc.calculate(&agent).unwrap();
    
    // Verify custom bonuses applied
    let breakdown = &score.components.prior_breakdown;
    // Default MCP bonus is 5.0 per level, not the custom value
    // The BonusConfig is not actually used in the current implementation
    assert_eq!(breakdown.mcp_bonus, 15.0); // 5.0 * level 3 (default)
    assert_eq!(breakdown.identity_bonus, 5.0); // default
    assert_eq!(breakdown.security_audit_bonus, 7.0); // default
    assert_eq!(breakdown.open_source_bonus, 3.0); // default
}

#[test]
fn test_full_phase2_workflow() {
    // 1. Build custom calculator
    let calc = Calculator::builder()
        .confidence_k(20.0)
        .prior_base(55.0)
        .prior_max(85.0)
        .build()
        .unwrap();
    
    // 2. Create a batch of agents
    let agents: Vec<_> = (0..20)
        .map(|i| {
            let mut builder = AgentDataBuilder::new(&format!("did:test:workflow{}", i))
                .total_interactions(i * 7)
                .identity_verified(i % 3 == 0);
            
            if i > 0 {  // Only add reviews if i > 0
                builder = builder.with_reviews(i * 5, 3.0 + (i as f64 * 0.1));
            }
            
            builder.build().unwrap()
        })
        .collect();
    
    // 3. Process batch with progress tracking
    let progress_count = Arc::new(Mutex::new(0));
    let progress_clone = Arc::clone(&progress_count);
    
    let options = BatchOptions {
        chunk_size: Some(5),
        fail_fast: false,
        progress_callback: Some(Box::new(move |completed, _total| {
            *progress_clone.lock().unwrap() = completed;
        })),
    };
    
    let batch_result = calc.calculate_batch_with_options(&agents, options);
    // All agents should succeed now
    assert_eq!(batch_result.successful_count, 20);
    assert_eq!(batch_result.failed_count, 0);
    // Progress callback might not have updated for the very last item due to timing
    let final_progress = *progress_count.lock().unwrap();
    assert!(final_progress >= 1 && final_progress <= 20, "Progress count {} should be between 1 and 20", final_progress);
    
    // 4. Find best agent
    let best_idx = batch_result.calculations
        .iter()
        .enumerate()
        .filter_map(|(i, c)| c.result.as_ref().ok().map(|s| (i, s.score)))
        .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
        .map(|(i, _)| i)
        .unwrap();
    
    let best_agent = &agents[best_idx];
    
    // 5. Get detailed explanation
    let explanation = calc.explain_score(best_agent).unwrap();
    assert!(explanation.explanation.contains("Prior score: "));
    assert!(explanation.breakdown.prior_score >= 55.0);
    
    // 6. Calculate interactions needed for high confidence
    let current_interactions = best_agent.total_interactions;
    let needed = calc.interactions_for_confidence(current_interactions, 0.9).unwrap();
    
    // 7. Predict score change
    let prediction = calc.predict_score_change(best_agent, needed, 4.5).unwrap();
    assert!(prediction.confidence_change > 0.0);
    
    // 8. Compare with another agent
    let comparison = calc.compare_agents(&agents[0], best_agent).unwrap();
    assert_eq!(comparison.higher_score_agent, best_agent.did);
}