reputation-types 0.1.0

Core types and data structures for the KnowThat Reputation Engine
Documentation
//! # Reputation Types
//! 
//! Core data types for the MCP agent reputation system.
//! 
//! This crate provides the fundamental data structures used throughout
//! the reputation system, including agent data, reputation scores,
//! and a builder for constructing agent instances.

// Forbid unsafe code to ensure memory safety
#![forbid(unsafe_code)]
//! 
//! ## Key Types
//! 
//! - [`AgentData`]: Represents an MCP agent with all reputation-relevant data
//! - [`ReputationScore`]: The calculated reputation score with confidence
//! - [`AgentDataBuilder`]: Fluent builder for constructing `AgentData`
//! 
//! ## Examples
//! 
//! ### Creating Agent Data
//! 
//! ```
//! use reputation_types::{AgentData, AgentDataBuilder};
//! 
//! // Using the builder (recommended)
//! let agent = AgentDataBuilder::new("did:example:123")
//!     .total_interactions(150)
//!     .with_reviews(100, 4.5)
//!     .mcp_level(2)
//!     .identity_verified(true)
//!     .build()
//!     .unwrap();
//! 
//! // Or use the convenience method
//! let agent = AgentData::builder("did:example:456")
//!     .total_interactions(60)
//!     .with_reviews(50, 3.8)
//!     .build()
//!     .unwrap();
//! ```

use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};

mod builder;
mod display;
pub use builder::{AgentDataBuilder, BuilderError};

/// Confidence level categorization based on confidence value
/// 
/// This enum categorizes the confidence score into three levels
/// to help users quickly understand how reliable a reputation score is.
/// 
/// # Thresholds
/// 
/// - `Low`: confidence < 0.2 (limited data)
/// - `Medium`: 0.2 ≤ confidence < 0.7 (moderate data)
/// - `High`: confidence ≥ 0.7 (substantial data)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConfidenceLevel {
    /// Low confidence - limited interaction data
    Low,
    /// Medium confidence - moderate interaction data
    Medium,
    /// High confidence - substantial interaction data
    High,
}

impl ConfidenceLevel {
    /// Determine confidence level from a confidence value
    pub fn from_confidence(confidence: f64) -> Self {
        if confidence < 0.2 {
            ConfidenceLevel::Low
        } else if confidence < 0.7 {
            ConfidenceLevel::Medium
        } else {
            ConfidenceLevel::High
        }
    }
}

/// Detailed breakdown of score components
/// 
/// Provides transparency into how the final reputation score
/// was calculated, showing the contribution of each component.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScoreComponents {
    /// Prior reputation score (based on credentials)
    pub prior_score: f64,
    /// Detailed breakdown of prior score calculation
    pub prior_breakdown: PriorBreakdown,
    /// Empirical score (based on reviews/ratings)
    pub empirical_score: f64,
    /// Raw confidence value (0-1)
    pub confidence_value: f64,
    /// Categorized confidence level
    pub confidence_level: ConfidenceLevel,
    /// Weight given to prior score (1 - confidence)
    pub prior_weight: f64,
    /// Weight given to empirical score (confidence)
    pub empirical_weight: f64,
}

/// Breakdown of prior score components
/// 
/// Shows how each credential and bonus contributes
/// to the total prior reputation score.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriorBreakdown {
    /// Base score (default: 50)
    pub base_score: f64,
    /// MCP level bonus (0-15 points)
    pub mcp_bonus: f64,
    /// Identity verification bonus (5 points)
    pub identity_bonus: f64,
    /// Security audit bonus (7 points)
    pub security_audit_bonus: f64,
    /// Open source bonus (3 points)
    pub open_source_bonus: f64,
    /// Age bonus for established agents (5 points)
    pub age_bonus: f64,
    /// Total prior score (capped at maximum)
    pub total: f64,
}

/// Represents an MCP agent with all reputation-relevant data
/// 
/// This struct contains all the information needed to calculate
/// a reputation score for an agent, including credentials,
/// review statistics, and verification status.
/// 
/// # Fields
/// 
/// - `did`: Decentralized identifier (must follow did:method:identifier format)
/// - `created_at`: When the agent was first registered
/// - `mcp_level`: MCP certification level (0-3, if any)
/// - `identity_verified`: Whether identity has been verified
/// - `security_audit_passed`: Whether security audit passed
/// - `open_source`: Whether the agent is open source
/// - `total_interactions`: Total number of interactions
/// - `total_reviews`: Total number of reviews received
/// - `average_rating`: Average rating (1.0-5.0 scale)
/// - `positive_reviews`: Count of positive reviews
/// - `negative_reviews`: Count of negative reviews
/// 
/// # Example
/// 
/// ```
/// use reputation_types::AgentData;
/// use chrono::Utc;
/// 
/// let agent = AgentData {
///     did: "did:example:123".to_string(),
///     created_at: Utc::now(),
///     mcp_level: Some(2),
///     identity_verified: true,
///     security_audit_passed: false,
///     open_source: true,
///     total_interactions: 500,
///     total_reviews: 100,
///     average_rating: Some(4.2),
///     positive_reviews: 80,
///     negative_reviews: 20,
/// };
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentData {
    /// Decentralized identifier for the agent
    pub did: String,
    
    /// When the agent was created/registered
    pub created_at: DateTime<Utc>,
    
    /// MCP certification level (0-3)
    pub mcp_level: Option<u8>,
    
    /// Whether the agent's identity has been verified
    pub identity_verified: bool,
    
    /// Whether the agent passed security audit
    pub security_audit_passed: bool,
    
    /// Whether the agent is open source
    pub open_source: bool,
    
    /// Total number of interactions with users
    pub total_interactions: u32,
    
    /// Total number of reviews received
    pub total_reviews: u32,
    
    /// Average rating on 1-5 scale
    pub average_rating: Option<f64>,
    
    /// Number of positive reviews
    pub positive_reviews: u32,
    
    /// Number of negative reviews
    pub negative_reviews: u32,
}

impl AgentData {
    /// Creates a new builder for constructing AgentData instances.
    /// 
    /// # Examples
    /// 
    /// ```
    /// use reputation_types::AgentData;
    /// 
    /// let agent = AgentData::builder("did:example:123")
    ///     .total_interactions(150)
    ///     .mcp_level(2)
    ///     .identity_verified(true)
    ///     .with_reviews(100, 4.5)
    ///     .build()
    ///     .unwrap();
    /// ```
    pub fn builder(did: impl Into<String>) -> AgentDataBuilder {
        AgentDataBuilder::new(did)
    }
}

/// The calculated reputation score for an agent
/// 
/// Contains the final reputation score along with detailed metadata
/// about how it was calculated, component breakdowns, and confidence level.
/// 
/// # Fields
/// 
/// - `score`: The final reputation score (0-100)
/// - `confidence`: How confident we are in the score (0-1)
/// - `level`: Categorized confidence level (Low/Medium/High)
/// - `components`: Detailed breakdown of score components
/// - `is_provisional`: Whether score is provisional (confidence < 0.2)
/// - `data_points`: Total data points used (interactions + reviews)
/// - `algorithm_version`: Version of algorithm used
/// - `calculated_at`: When the score was calculated
/// 
/// # Interpretation
/// 
/// ## Score Ranges
/// - 0-20: Very poor reputation
/// - 20-40: Poor reputation
/// - 40-60: Average reputation
/// - 60-80: Good reputation
/// - 80-100: Excellent reputation
/// 
/// ## Confidence Levels
/// - Low (0-0.2): Limited data, provisional score
/// - Medium (0.2-0.7): Moderate confidence
/// - High (0.7-1.0): High confidence, substantial data
/// 
/// # Example
/// 
/// ```
/// use reputation_types::{ReputationScore, ConfidenceLevel, ScoreComponents, PriorBreakdown};
/// use chrono::Utc;
/// 
/// # let components = ScoreComponents {
/// #     prior_score: 65.0,
/// #     prior_breakdown: PriorBreakdown {
/// #         base_score: 50.0,
/// #         mcp_bonus: 10.0,
/// #         identity_bonus: 5.0,
/// #         security_audit_bonus: 0.0,
/// #         open_source_bonus: 0.0,
/// #         age_bonus: 0.0,
/// #         total: 65.0,
/// #     },
/// #     empirical_score: 80.0,
/// #     confidence_value: 0.85,
/// #     confidence_level: ConfidenceLevel::High,
/// #     prior_weight: 0.15,
/// #     empirical_weight: 0.85,
/// # };
/// #
/// let score = ReputationScore {
///     score: 75.5,
///     confidence: 0.85,
///     level: ConfidenceLevel::High,
///     components,
///     is_provisional: false,
///     data_points: 150,
///     algorithm_version: "1.0.0".to_string(),
///     calculated_at: Utc::now(),
/// };
/// 
/// if score.is_provisional {
///     println!("Provisional score: {:.1} (needs more data)", score.score);
/// } else {
///     println!("{:?} confidence score: {:.1}", score.level, score.score);
/// }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReputationScore {
    /// The final reputation score (0-100 scale)
    pub score: f64,
    
    /// Confidence in the score based on data availability (0-1)
    pub confidence: f64,
    
    /// Categorized confidence level
    pub level: ConfidenceLevel,
    
    /// Detailed breakdown of score components
    pub components: ScoreComponents,
    
    /// Whether this is a provisional score (confidence < 0.2)
    pub is_provisional: bool,
    
    /// Total data points used in calculation
    pub data_points: u32,
    
    /// Version of the algorithm used for calculation
    pub algorithm_version: String,
    
    /// Timestamp when the score was calculated
    pub calculated_at: DateTime<Utc>,
}

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

    #[test]
    fn test_confidence_level_from_confidence() {
        // Test Low level
        assert_eq!(ConfidenceLevel::from_confidence(0.0), ConfidenceLevel::Low);
        assert_eq!(ConfidenceLevel::from_confidence(0.1), ConfidenceLevel::Low);
        assert_eq!(ConfidenceLevel::from_confidence(0.19), ConfidenceLevel::Low);
        
        // Test Medium level
        assert_eq!(ConfidenceLevel::from_confidence(0.2), ConfidenceLevel::Medium);
        assert_eq!(ConfidenceLevel::from_confidence(0.5), ConfidenceLevel::Medium);
        assert_eq!(ConfidenceLevel::from_confidence(0.69), ConfidenceLevel::Medium);
        
        // Test High level
        assert_eq!(ConfidenceLevel::from_confidence(0.7), ConfidenceLevel::High);
        assert_eq!(ConfidenceLevel::from_confidence(0.9), ConfidenceLevel::High);
        assert_eq!(ConfidenceLevel::from_confidence(1.0), ConfidenceLevel::High);
    }

    #[test]
    fn test_confidence_level_boundaries() {
        // Test exact boundaries
        assert_eq!(ConfidenceLevel::from_confidence(0.199999), ConfidenceLevel::Low);
        assert_eq!(ConfidenceLevel::from_confidence(0.2), ConfidenceLevel::Medium);
        
        assert_eq!(ConfidenceLevel::from_confidence(0.699999), ConfidenceLevel::Medium);
        assert_eq!(ConfidenceLevel::from_confidence(0.7), ConfidenceLevel::High);
    }

    #[test]
    fn test_confidence_level_serialization() {
        // Test that ConfidenceLevel can be serialized and deserialized
        let levels = vec![
            ConfidenceLevel::Low,
            ConfidenceLevel::Medium,
            ConfidenceLevel::High,
        ];
        
        for level in levels {
            let serialized = serde_json::to_string(&level).unwrap();
            let deserialized: ConfidenceLevel = serde_json::from_str(&serialized).unwrap();
            assert_eq!(level, deserialized);
        }
    }
}