reputation-core 0.1.0

Core calculation engine for the KnowThat Reputation System with advanced scoring algorithms
Documentation
//! Prior score calculation module
//! 
//! This module handles the calculation of prior reputation scores based on
//! agent credentials and bonuses.

use chrono::Utc;
use reputation_types::{AgentData, PriorBreakdown};

/// Calculates the prior reputation score for an agent with detailed breakdown
/// 
/// Prior score includes:
/// - Base score (default 50 points)
/// - MCP level bonus (0-15 points)
/// - Identity verification bonus (+5 points)
/// - Security audit bonus (+7 points)
/// - Open source bonus (+3 points)
/// - Age bonus (+5 points for agents > 365 days old)
/// 
/// The total prior score is capped at a configurable maximum (default 80 points).
/// 
/// Returns a PriorBreakdown struct with all component scores.
pub(crate) fn calculate_prior_detailed(
    agent: &AgentData,
    prior_base: f64,
    prior_max: f64,
) -> PriorBreakdown {
    let base_score = prior_base;
    
    // MCP level bonus (0-15 points)
    let mcp_bonus = if let Some(level) = agent.mcp_level {
        match level {
            1 => 5.0,
            2 => 10.0,
            3 => 15.0,
            _ => 0.0,
        }
    } else {
        0.0
    };

    // Identity verified bonus (+5 points)
    let identity_bonus = if agent.identity_verified { 5.0 } else { 0.0 };

    // Security audit passed bonus (+7 points)
    let security_audit_bonus = if agent.security_audit_passed { 7.0 } else { 0.0 };

    // Open source bonus (+3 points)
    let open_source_bonus = if agent.open_source { 3.0 } else { 0.0 };

    // Age bonus (+5 points for agents > 365 days old)
    let age_days = (Utc::now() - agent.created_at).num_days();
    let age_bonus = if age_days > 365 { 5.0 } else { 0.0 };

    // Calculate uncapped total
    let uncapped_total = base_score + mcp_bonus + identity_bonus + 
                        security_audit_bonus + open_source_bonus + age_bonus;

    // Cap at maximum prior score
    let total = uncapped_total.min(prior_max);

    PriorBreakdown {
        base_score,
        mcp_bonus,
        identity_bonus,
        security_audit_bonus,
        open_source_bonus,
        age_bonus,
        total,
    }
}

/// Calculates the prior reputation score for an agent (simple version)
/// 
/// This is a compatibility wrapper that returns just the total score.
#[cfg(test)]
pub(crate) fn calculate_prior(
    agent: &AgentData,
    prior_base: f64,
    prior_max: f64,
) -> f64 {
    calculate_prior_detailed(agent, prior_base, prior_max).total
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Duration;

    fn create_test_agent() -> AgentData {
        AgentData {
            did: "did:test:123".to_string(),
            created_at: Utc::now() - Duration::days(1),
            mcp_level: None,
            identity_verified: false,
            security_audit_passed: false,
            open_source: false,
            total_interactions: 0,
            total_reviews: 0,
            average_rating: None,
            positive_reviews: 0,
            negative_reviews: 0,
        }
    }

    #[test]
    fn test_base_prior_score() {
        let agent = create_test_agent();
        let score = calculate_prior(&agent, 50.0, 80.0);
        assert_eq!(score, 50.0);
    }

    #[test]
    fn test_mcp_level_bonuses() {
        let mut agent = create_test_agent();
        
        agent.mcp_level = Some(1);
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 55.0);
        
        agent.mcp_level = Some(2);
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 60.0);
        
        agent.mcp_level = Some(3);
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 65.0);
        
        agent.mcp_level = Some(4); // Invalid level
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 50.0);
    }

    #[test]
    fn test_identity_bonuses() {
        let mut agent = create_test_agent();
        
        agent.identity_verified = true;
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 55.0);
        
        agent.security_audit_passed = true;
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 62.0);
        
        agent.open_source = true;
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 65.0);
    }

    #[test]
    fn test_age_bonus() {
        let mut agent = create_test_agent();
        
        // Young agent (no bonus)
        agent.created_at = Utc::now() - Duration::days(100);
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 50.0);
        
        // Old agent (gets bonus)
        agent.created_at = Utc::now() - Duration::days(400);
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 55.0);
    }

    #[test]
    fn test_prior_cap() {
        let mut agent = create_test_agent();
        
        // Max out all bonuses
        agent.mcp_level = Some(3); // +15
        agent.identity_verified = true; // +5
        agent.security_audit_passed = true; // +7
        agent.open_source = true; // +3
        agent.created_at = Utc::now() - Duration::days(400); // +5
        
        // Total would be 50 + 15 + 5 + 7 + 3 + 5 = 85, but capped at 80
        assert_eq!(calculate_prior(&agent, 50.0, 80.0), 80.0);
    }

    #[test]
    fn test_custom_base_and_cap() {
        let agent = create_test_agent();
        
        // Test with different base
        assert_eq!(calculate_prior(&agent, 60.0, 90.0), 60.0);
        
        // Test with bonuses and custom cap
        let mut agent = create_test_agent();
        agent.mcp_level = Some(3);
        agent.identity_verified = true;
        agent.security_audit_passed = true;
        
        // 60 + 15 + 5 + 7 = 87, not capped with 90 limit
        assert_eq!(calculate_prior(&agent, 60.0, 90.0), 87.0);
        
        // Same bonuses but lower cap
        assert_eq!(calculate_prior(&agent, 60.0, 85.0), 85.0);
    }

    #[test]
    fn test_detailed_breakdown() {
        let mut agent = create_test_agent();
        agent.mcp_level = Some(2); // +10
        agent.identity_verified = true; // +5
        agent.open_source = true; // +3
        
        let breakdown = calculate_prior_detailed(&agent, 50.0, 80.0);
        
        assert_eq!(breakdown.base_score, 50.0);
        assert_eq!(breakdown.mcp_bonus, 10.0);
        assert_eq!(breakdown.identity_bonus, 5.0);
        assert_eq!(breakdown.security_audit_bonus, 0.0);
        assert_eq!(breakdown.open_source_bonus, 3.0);
        assert_eq!(breakdown.age_bonus, 0.0);
        assert_eq!(breakdown.total, 68.0); // 50 + 10 + 5 + 3
    }

    #[test]
    fn test_breakdown_with_cap() {
        let mut agent = create_test_agent();
        agent.mcp_level = Some(3); // +15
        agent.identity_verified = true; // +5
        agent.security_audit_passed = true; // +7
        agent.open_source = true; // +3
        agent.created_at = Utc::now() - Duration::days(400); // +5
        
        let breakdown = calculate_prior_detailed(&agent, 50.0, 80.0);
        
        // Individual bonuses should be correct
        assert_eq!(breakdown.mcp_bonus, 15.0);
        assert_eq!(breakdown.identity_bonus, 5.0);
        assert_eq!(breakdown.security_audit_bonus, 7.0);
        assert_eq!(breakdown.open_source_bonus, 3.0);
        assert_eq!(breakdown.age_bonus, 5.0);
        
        // Total should be capped
        assert_eq!(breakdown.total, 80.0); // Capped, not 85
    }
}