reputation-core 0.1.0

Core calculation engine for the KnowThat Reputation System with advanced scoring algorithms
Documentation
use reputation_core::Calculator;
use reputation_types::AgentDataBuilder;

#[test]
fn test_interactions_for_confidence_basic() {
    let calc = Calculator::default(); // k = 15
    
    // From 0, need 15 interactions for 50% confidence
    let needed = calc.interactions_for_confidence(0, 0.5).unwrap();
    assert_eq!(needed, 15);
    
    // Already have 10, need 5 more for 50%
    let needed = calc.interactions_for_confidence(10, 0.5).unwrap();
    assert_eq!(needed, 5);
    
    // Already at or above target
    let needed = calc.interactions_for_confidence(15, 0.5).unwrap();
    assert_eq!(needed, 0);
    
    let needed = calc.interactions_for_confidence(20, 0.5).unwrap();
    assert_eq!(needed, 0);
}

#[test]
fn test_interactions_for_confidence_targets() {
    let calc = Calculator::default();
    
    // Low confidence (20%)
    let needed = calc.interactions_for_confidence(0, 0.2).unwrap();
    assert_eq!(needed, 4); // ceil(0.2 * 15 / 0.8) = 4
    
    // Medium confidence (70%)
    let needed = calc.interactions_for_confidence(0, 0.7).unwrap();
    assert_eq!(needed, 35); // ceil(0.7 * 15 / 0.3) = 35
    
    // High confidence (90%)
    let needed = calc.interactions_for_confidence(0, 0.9).unwrap();
    // Due to floating point precision, ceiling of 135.0 might be 136
    assert_eq!(needed, 136); // ceil(0.9 * 15 / 0.1) ≈ 136
    
    // Very high confidence (99%)
    let needed = calc.interactions_for_confidence(0, 0.99).unwrap();
    assert_eq!(needed, 1485); // ceil(0.99 * 15 / 0.01) = 1485
}

#[test]
fn test_interactions_for_confidence_edge_cases() {
    let calc = Calculator::default();
    
    // Zero target
    let needed = calc.interactions_for_confidence(100, 0.0).unwrap();
    assert_eq!(needed, 0);
    
    // Cannot reach 1.0
    assert!(calc.interactions_for_confidence(0, 1.0).is_err());
    
    // Invalid targets
    assert!(calc.interactions_for_confidence(0, -0.1).is_err());
    assert!(calc.interactions_for_confidence(0, 1.1).is_err());
}

#[test]
fn test_confidence_after_interactions() {
    let calc = Calculator::default();
    
    // Starting from 0
    assert_eq!(calc.confidence_after_interactions(0, 0), 0.0);
    assert!((calc.confidence_after_interactions(0, 15) - 0.5).abs() < 0.0001);
    assert!((calc.confidence_after_interactions(0, 135) - 0.9).abs() < 0.0001);
    
    // Starting with existing interactions
    assert!((calc.confidence_after_interactions(15, 15) - 0.6667).abs() < 0.0001);
    assert!((calc.confidence_after_interactions(35, 100) - 0.9).abs() < 0.0001);
    
    // Large numbers
    let conf = calc.confidence_after_interactions(10000, 5000);
    assert!(conf > 0.99);
}

#[test]
fn test_explain_score_basic() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:explain")
        .with_reviews(50, 4.0)
        .total_interactions(100)
        .build()
        .unwrap();
    
    let explanation = calc.explain_score(&agent).unwrap();
    
    // Check structure
    assert!(explanation.final_score > 0.0);
    assert!(explanation.confidence > 0.0);
    assert!(!explanation.explanation.is_empty());
    
    // Check content
    assert!(explanation.explanation.contains("Reputation score"));
    assert!(explanation.explanation.contains("Prior score"));
    assert!(explanation.explanation.contains("Empirical score"));
    assert!(explanation.explanation.contains("Confidence Level"));
    assert!(explanation.explanation.contains("Weighting"));
}

#[test]
fn test_explain_score_with_credentials() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:credentials")
        .with_reviews(30, 4.5)
        .total_interactions(50)
        .mcp_level(2)
        .identity_verified(true)
        .security_audit_passed(true)
        .open_source(true)
        .build()
        .unwrap();
    
    let explanation = calc.explain_score(&agent).unwrap();
    
    // Should mention all credentials
    assert!(explanation.explanation.contains("MCP bonus"));
    assert!(explanation.explanation.contains("Identity verified"));
    assert!(explanation.explanation.contains("Security audit"));
    assert!(explanation.explanation.contains("Open source"));
}

#[test]
fn test_explain_score_no_reviews() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:new")
        .build()
        .unwrap();
    
    let explanation = calc.explain_score(&agent).unwrap();
    
    assert!(explanation.explanation.contains("No reviews yet"));
    assert_eq!(explanation.confidence, 0.0);
    assert_eq!(explanation.final_score, 50.0); // Base prior score
}

#[test]
fn test_explain_score_provisional() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:provisional")
        .with_reviews(2, 4.0)
        .total_interactions(2)
        .build()
        .unwrap();
    
    let explanation = calc.explain_score(&agent).unwrap();
    
    assert!(explanation.explanation.contains("Provisional"));
    assert!(explanation.confidence < 0.2);
}

#[test]
fn test_predict_score_change_positive() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:predict")
        .with_reviews(50, 3.0)
        .total_interactions(50)
        .build()
        .unwrap();
    
    // Add good reviews
    let prediction = calc.predict_score_change(&agent, 10, 5.0).unwrap();
    
    assert!(prediction.score_change > 0.0);
    assert!(prediction.confidence_change > 0.0);
    assert_eq!(prediction.reviews_added, 10);
    assert_eq!(prediction.rating_used, 5.0);
    assert!(prediction.predicted_score > prediction.current_score);
}

#[test]
fn test_predict_score_change_negative() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:predict")
        .with_reviews(50, 4.0)
        .total_interactions(50)
        .build()
        .unwrap();
    
    // Add bad reviews
    let prediction = calc.predict_score_change(&agent, 10, 1.0).unwrap();
    
    assert!(prediction.score_change < 0.0);
    assert!(prediction.predicted_score < prediction.current_score);
}

#[test]
fn test_predict_score_change_first_reviews() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:new")
        .build()
        .unwrap();
    
    let prediction = calc.predict_score_change(&agent, 5, 4.5).unwrap();
    
    assert_ne!(prediction.score_change, 0.0);
    assert!(prediction.confidence_change > 0.0);
    assert_eq!(prediction.current_score, 50.0); // Base prior
}

#[test]
fn test_predict_score_change_invalid_rating() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:predict")
        .with_reviews(10, 4.0)
        .total_interactions(10)
        .build()
        .unwrap();
    
    assert!(calc.predict_score_change(&agent, 5, 0.0).is_err());
    assert!(calc.predict_score_change(&agent, 5, 6.0).is_err());
    assert!(calc.predict_score_change(&agent, 5, -1.0).is_err());
}

#[test]
fn test_compare_agents_basic() {
    let calc = Calculator::default();
    
    let agent_a = AgentDataBuilder::new("did:test:alice")
        .with_reviews(100, 4.0)
        .total_interactions(150)
        .build()
        .unwrap();
    
    let agent_b = AgentDataBuilder::new("did:test:bob")
        .with_reviews(50, 4.5)
        .total_interactions(60)
        .build()
        .unwrap();
    
    let comparison = calc.compare_agents(&agent_a, &agent_b).unwrap();
    
    assert_eq!(comparison.agent_a_id, "did:test:alice");
    assert_eq!(comparison.agent_b_id, "did:test:bob");
    assert!(comparison.score_a > 0.0);
    assert!(comparison.score_b > 0.0);
    assert_ne!(comparison.score_difference, 0.0);
}

#[test]
fn test_compare_agents_confidence_vs_score() {
    let calc = Calculator::default();
    
    // High score, low confidence
    let agent_a = AgentDataBuilder::new("did:test:highscore")
        .with_reviews(5, 5.0)
        .total_interactions(5)
        .build()
        .unwrap();
    
    // Lower score, high confidence
    let agent_b = AgentDataBuilder::new("did:test:reliable")
        .with_reviews(200, 4.0)
        .total_interactions(300)
        .build()
        .unwrap();
    
    let comparison = calc.compare_agents(&agent_a, &agent_b).unwrap();
    
    // B actually has higher score due to high confidence weight
    // A: ~62.5 (low confidence, perfect rating)
    // B: ~73.8 (high confidence, good rating)
    assert!(comparison.score_a < comparison.score_b);
    assert_eq!(comparison.higher_score_agent, "did:test:reliable");
    
    assert!(comparison.confidence_a < comparison.confidence_b);
    assert_eq!(comparison.more_reliable_agent, "did:test:reliable");
}

#[test]
fn test_compare_identical_agents() {
    let calc = Calculator::default();
    
    let agent_a = AgentDataBuilder::new("did:test:twin1")
        .with_reviews(50, 4.0)
        .total_interactions(100)
        .build()
        .unwrap();
    
    let agent_b = AgentDataBuilder::new("did:test:twin2")
        .with_reviews(50, 4.0)
        .total_interactions(100)
        .build()
        .unwrap();
    
    let comparison = calc.compare_agents(&agent_a, &agent_b).unwrap();
    
    assert_eq!(comparison.score_a, comparison.score_b);
    assert_eq!(comparison.score_difference, 0.0);
    assert_eq!(comparison.confidence_a, comparison.confidence_b);
}

#[test]
fn test_utility_methods_with_presets() {
    // Test with different calculator presets
    let conservative = Calculator::builder()
        .preset(reputation_core::CalculatorPreset::Conservative)
        .build()
        .unwrap();
    
    let aggressive = Calculator::builder()
        .preset(reputation_core::CalculatorPreset::Aggressive)
        .build()
        .unwrap();
    
    // Same agent, different calculators
    let agent = AgentDataBuilder::new("did:test:preset")
        .with_reviews(30, 4.0)
        .total_interactions(50)
        .build()
        .unwrap();
    
    // Conservative needs more interactions for same confidence
    let needed_conservative = conservative.interactions_for_confidence(50, 0.8).unwrap();
    let needed_aggressive = aggressive.interactions_for_confidence(50, 0.8).unwrap();
    
    assert!(needed_conservative > needed_aggressive);
    
    // Explanations should reflect different confidence levels
    let explain_conservative = conservative.explain_score(&agent).unwrap();
    let explain_aggressive = aggressive.explain_score(&agent).unwrap();
    
    assert!(explain_conservative.confidence < explain_aggressive.confidence);
}

#[test]
fn test_large_numbers() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:large")
        .with_reviews(10000, 4.2)
        .total_interactions(50000)
        .build()
        .unwrap();
    
    // Should handle large numbers without overflow
    let explanation = calc.explain_score(&agent).unwrap();
    assert!(explanation.confidence > 0.99);
    
    let prediction = calc.predict_score_change(&agent, 1000, 3.0).unwrap();
    assert!(prediction.score_change < 0.0); // Should still affect score slightly
    
    let needed = calc.interactions_for_confidence(50000, 0.9999).unwrap();
    assert!(needed > 0); // Even with 50k, still need more for 99.99%
}

#[test]
fn test_score_explanation_formatting() {
    let calc = Calculator::default();
    
    let agent = AgentDataBuilder::new("did:test:format")
        .with_reviews(75, 4.3)
        .total_interactions(100)
        .mcp_level(1)
        .identity_verified(true)
        .build()
        .unwrap();
    
    let explanation = calc.explain_score(&agent).unwrap();
    
    // Check for proper sections
    assert!(explanation.explanation.contains("📊 Score Components"));
    assert!(explanation.explanation.contains("🎯 Confidence Level"));
    assert!(explanation.explanation.contains("⚖️ Weighting"));
    
    // Check numerical formatting
    assert!(explanation.explanation.contains("4.3/5.0")); // Rating format
    assert!(explanation.explanation.contains("%")); // Percentage format
}