reputation_core/calculator/
builder.rs

1//! Enhanced builder pattern for Calculator construction
2//! 
3//! Provides a fluent API for creating Calculator instances with
4//! custom configurations, presets, and bonus settings.
5
6use crate::{Calculator, Result, BuilderError, CalculatorConfig};
7
8/// Configuration for prior score bonuses
9#[derive(Debug, Clone)]
10pub struct BonusConfig {
11    /// MCP level bonuses (per level)
12    pub mcp_bonus_per_level: f64,
13    /// Identity verification bonus
14    pub identity_bonus: f64,
15    /// Security audit bonus
16    pub security_audit_bonus: f64,
17    /// Open source bonus
18    pub open_source_bonus: f64,
19    /// Age bonus (for agents > 365 days)
20    pub age_bonus: f64,
21}
22
23impl Default for BonusConfig {
24    fn default() -> Self {
25        Self {
26            mcp_bonus_per_level: 5.0,
27            identity_bonus: 5.0,
28            security_audit_bonus: 7.0,
29            open_source_bonus: 3.0,
30            age_bonus: 5.0,
31        }
32    }
33}
34
35/// Preset configurations for common scenarios
36#[derive(Debug, Clone, Copy, PartialEq)]
37pub enum CalculatorPreset {
38    /// Fast confidence growth for testing (k=5.0)
39    Testing,
40    /// Slow confidence growth for conservative scoring (k=30.0)
41    Conservative,
42    /// Medium confidence growth for balanced scoring (k=10.0)
43    Aggressive,
44    /// Standard default configuration
45    Default,
46}
47
48/// Enhanced builder for creating Calculator instances
49/// 
50/// Provides a fluent API with validation, presets, and custom bonus configurations.
51/// 
52/// # Example
53/// 
54/// ```
55/// use reputation_core::{Calculator, CalculatorPreset};
56/// 
57/// // Using presets
58/// let calc = Calculator::builder()
59///     .preset(CalculatorPreset::Testing)
60///     .build()
61///     .unwrap();
62/// 
63/// // Custom configuration
64/// let calc = Calculator::builder()
65///     .confidence_k(20.0)
66///     .prior_base(60.0)
67///     .prior_max(90.0)
68///     .build()
69///     .unwrap();
70/// ```
71#[derive(Debug, Clone)]
72pub struct CalculatorBuilder {
73    confidence_k: Option<f64>,
74    prior_base: Option<f64>,
75    prior_max: Option<f64>,
76    bonuses: Option<BonusConfig>,
77    preset_applied: Option<CalculatorPreset>,
78}
79
80impl Default for CalculatorBuilder {
81    fn default() -> Self {
82        Self {
83            confidence_k: None,
84            prior_base: None,
85            prior_max: None,
86            bonuses: None,
87            preset_applied: None,
88        }
89    }
90}
91
92impl CalculatorBuilder {
93    /// Create a new builder with no configuration
94    pub fn new() -> Self {
95        Self::default()
96    }
97    
98    /// Set confidence growth parameter (k in the formula)
99    /// 
100    /// Higher values result in slower confidence growth.
101    /// Must be positive.
102    /// 
103    /// # Example
104    /// 
105    /// ```
106    /// use reputation_core::Calculator;
107    /// 
108    /// let calc = Calculator::builder()
109    ///     .confidence_k(20.0)  // Slower growth
110    ///     .build()
111    ///     .unwrap();
112    /// ```
113    pub fn confidence_k(mut self, k: f64) -> Self {
114        self.confidence_k = Some(k);
115        self
116    }
117    
118    /// Set base prior score (starting reputation)
119    /// 
120    /// Must be between 0 and 100.
121    /// 
122    /// # Example
123    /// 
124    /// ```
125    /// use reputation_core::Calculator;
126    /// 
127    /// let calc = Calculator::builder()
128    ///     .prior_base(60.0)  // Higher starting score
129    ///     .build()
130    ///     .unwrap();
131    /// ```
132    pub fn prior_base(mut self, base: f64) -> Self {
133        self.prior_base = Some(base);
134        self
135    }
136    
137    /// Set maximum prior score (cap for credential bonuses)
138    /// 
139    /// Must be between prior_base and 100.
140    /// 
141    /// # Example
142    /// 
143    /// ```
144    /// use reputation_core::Calculator;
145    /// 
146    /// let calc = Calculator::builder()
147    ///     .prior_base(50.0)
148    ///     .prior_max(85.0)  // Higher ceiling
149    ///     .build()
150    ///     .unwrap();
151    /// ```
152    pub fn prior_max(mut self, max: f64) -> Self {
153        self.prior_max = Some(max);
154        self
155    }
156    
157    /// Set custom bonus configuration
158    /// 
159    /// Allows fine-tuning of individual credential bonuses.
160    /// 
161    /// # Example
162    /// 
163    /// ```
164    /// use reputation_core::{Calculator, BonusConfig};
165    /// 
166    /// let bonuses = BonusConfig {
167    ///     mcp_bonus_per_level: 7.0,  // Higher MCP bonus
168    ///     identity_bonus: 10.0,      // Higher identity bonus
169    ///     ..Default::default()
170    /// };
171    /// 
172    /// let calc = Calculator::builder()
173    ///     .prior_bonuses(bonuses)
174    ///     .build()
175    ///     .unwrap();
176    /// ```
177    pub fn prior_bonuses(mut self, bonuses: BonusConfig) -> Self {
178        self.bonuses = Some(bonuses);
179        self
180    }
181    
182    /// Apply a preset configuration
183    /// 
184    /// Presets provide tested configurations for common scenarios.
185    /// Individual settings can still be overridden after applying a preset.
186    /// 
187    /// # Example
188    /// 
189    /// ```
190    /// use reputation_core::{Calculator, CalculatorPreset};
191    /// 
192    /// // Use testing preset with custom prior_base
193    /// let calc = Calculator::builder()
194    ///     .preset(CalculatorPreset::Testing)
195    ///     .prior_base(55.0)  // Override preset's prior_base
196    ///     .build()
197    ///     .unwrap();
198    /// ```
199    pub fn preset(mut self, preset: CalculatorPreset) -> Self {
200        self.preset_applied = Some(preset);
201        
202        // Apply preset values (can be overridden by subsequent calls)
203        match preset {
204            CalculatorPreset::Testing => {
205                self.confidence_k = Some(5.0);
206                self.prior_base = Some(50.0);
207                self.prior_max = Some(80.0);
208            }
209            CalculatorPreset::Conservative => {
210                self.confidence_k = Some(30.0);
211                self.prior_base = Some(60.0);
212                self.prior_max = Some(80.0);
213            }
214            CalculatorPreset::Aggressive => {
215                self.confidence_k = Some(10.0);
216                self.prior_base = Some(40.0);
217                self.prior_max = Some(90.0);
218            }
219            CalculatorPreset::Default => {
220                self.confidence_k = Some(15.0);
221                self.prior_base = Some(50.0);
222                self.prior_max = Some(80.0);
223            }
224        }
225        
226        self
227    }
228    
229    /// Load configuration from a CalculatorConfig
230    /// 
231    /// # Example
232    /// 
233    /// ```
234    /// use reputation_core::{Calculator, CalculatorConfig};
235    /// 
236    /// let config = CalculatorConfig::default();
237    /// let calc = Calculator::builder()
238    ///     .from_config(config)
239    ///     .build()
240    ///     .unwrap();
241    /// ```
242    pub fn from_config(mut self, config: CalculatorConfig) -> Self {
243        self.confidence_k = Some(config.confidence_k);
244        self.prior_base = Some(config.prior_base);
245        self.prior_max = Some(config.prior_max);
246        self
247    }
248    
249    /// Get the current configuration (for inspection)
250    pub fn config(&self) -> CalculatorConfig {
251        CalculatorConfig {
252            confidence_k: self.confidence_k.unwrap_or(15.0),
253            prior_base: self.prior_base.unwrap_or(50.0),
254            prior_max: self.prior_max.unwrap_or(80.0),
255        }
256    }
257    
258    /// Build the Calculator with validation
259    /// 
260    /// # Errors
261    /// 
262    /// Returns an error if:
263    /// - confidence_k <= 0
264    /// - prior_base < 0 or > 100
265    /// - prior_max < prior_base or > 100
266    /// 
267    /// # Example
268    /// 
269    /// ```
270    /// use reputation_core::Calculator;
271    /// 
272    /// let result = Calculator::builder()
273    ///     .confidence_k(-5.0)  // Invalid!
274    ///     .build();
275    /// 
276    /// assert!(result.is_err());
277    /// ```
278    pub fn build(self) -> Result<Calculator> {
279        let config = self.config();
280        
281        // Validate configuration
282        if config.confidence_k <= 0.0 {
283            return Err(BuilderError::InvalidConfig(
284                "confidence_k must be positive".to_string()
285            ).into());
286        }
287        
288        if config.prior_base < 0.0 || config.prior_base > 100.0 {
289            return Err(BuilderError::InvalidConfig(
290                "prior_base must be between 0 and 100".to_string()
291            ).into());
292        }
293        
294        if config.prior_max <= config.prior_base {
295            return Err(BuilderError::InvalidConfig(
296                format!("prior_max must be greater than prior_base ({})", config.prior_base)
297            ).into());
298        }
299        
300        if config.prior_max > 100.0 {
301            return Err(BuilderError::InvalidConfig(
302                "prior_max must not exceed 100".to_string()
303            ).into());
304        }
305        
306        // Create calculator
307        // Note: In a real implementation, we might pass the bonus config to the Calculator
308        // For now, we'll use the standard constructor
309        Calculator::new(config.confidence_k, config.prior_base, config.prior_max)
310    }
311    
312    /// Reset the builder to default state
313    pub fn reset(mut self) -> Self {
314        self.confidence_k = None;
315        self.prior_base = None;
316        self.prior_max = None;
317        self.bonuses = None;
318        self.preset_applied = None;
319        self
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326    
327    #[test]
328    fn test_builder_defaults() {
329        let calc = CalculatorBuilder::new().build().unwrap();
330        assert_eq!(calc.confidence_k, 15.0);
331        assert_eq!(calc.prior_base, 50.0);
332        assert_eq!(calc.prior_max, 80.0);
333    }
334    
335    #[test]
336    fn test_builder_custom_values() {
337        let calc = CalculatorBuilder::new()
338            .confidence_k(25.0)
339            .prior_base(55.0)
340            .prior_max(85.0)
341            .build()
342            .unwrap();
343            
344        assert_eq!(calc.confidence_k, 25.0);
345        assert_eq!(calc.prior_base, 55.0);
346        assert_eq!(calc.prior_max, 85.0);
347    }
348    
349    #[test]
350    fn test_preset_testing() {
351        let calc = CalculatorBuilder::new()
352            .preset(CalculatorPreset::Testing)
353            .build()
354            .unwrap();
355            
356        assert_eq!(calc.confidence_k, 5.0);
357        assert_eq!(calc.prior_base, 50.0);
358        assert_eq!(calc.prior_max, 80.0);
359    }
360    
361    #[test]
362    fn test_preset_conservative() {
363        let calc = CalculatorBuilder::new()
364            .preset(CalculatorPreset::Conservative)
365            .build()
366            .unwrap();
367            
368        assert_eq!(calc.confidence_k, 30.0);
369        assert_eq!(calc.prior_base, 60.0);
370        assert_eq!(calc.prior_max, 80.0);
371    }
372    
373    #[test]
374    fn test_preset_aggressive() {
375        let calc = CalculatorBuilder::new()
376            .preset(CalculatorPreset::Aggressive)
377            .build()
378            .unwrap();
379            
380        assert_eq!(calc.confidence_k, 10.0);
381        assert_eq!(calc.prior_base, 40.0);
382        assert_eq!(calc.prior_max, 90.0);
383    }
384    
385    #[test]
386    fn test_preset_override() {
387        // Apply preset then override specific values
388        let calc = CalculatorBuilder::new()
389            .preset(CalculatorPreset::Testing)
390            .confidence_k(8.0)  // Override preset
391            .build()
392            .unwrap();
393            
394        assert_eq!(calc.confidence_k, 8.0);  // Overridden
395        assert_eq!(calc.prior_base, 50.0);   // From preset
396        assert_eq!(calc.prior_max, 80.0);    // From preset
397    }
398    
399    #[test]
400    fn test_validation_confidence_k() {
401        let result = CalculatorBuilder::new()
402            .confidence_k(0.0)
403            .build();
404        assert!(result.is_err());
405        
406        let result = CalculatorBuilder::new()
407            .confidence_k(-5.0)
408            .build();
409        assert!(result.is_err());
410    }
411    
412    #[test]
413    fn test_validation_prior_base() {
414        let result = CalculatorBuilder::new()
415            .prior_base(-10.0)
416            .build();
417        assert!(result.is_err());
418        
419        let result = CalculatorBuilder::new()
420            .prior_base(105.0)
421            .build();
422        assert!(result.is_err());
423    }
424    
425    #[test]
426    fn test_validation_prior_max() {
427        // Max less than base
428        let result = CalculatorBuilder::new()
429            .prior_base(60.0)
430            .prior_max(50.0)
431            .build();
432        assert!(result.is_err());
433        
434        // Max equal to base
435        let result = CalculatorBuilder::new()
436            .prior_base(60.0)
437            .prior_max(60.0)
438            .build();
439        assert!(result.is_err());
440        
441        // Max over 100
442        let result = CalculatorBuilder::new()
443            .prior_max(105.0)
444            .build();
445        assert!(result.is_err());
446    }
447    
448    #[test]
449    fn test_builder_clone() {
450        let builder = CalculatorBuilder::new()
451            .confidence_k(20.0)
452            .prior_base(60.0);
453            
454        let builder2 = builder.clone()
455            .prior_max(85.0);
456            
457        let calc1 = builder.build().unwrap();
458        let calc2 = builder2.build().unwrap();
459        
460        assert_eq!(calc1.confidence_k, 20.0);
461        assert_eq!(calc1.prior_max, 80.0);  // Default
462        
463        assert_eq!(calc2.confidence_k, 20.0);
464        assert_eq!(calc2.prior_max, 85.0);  // Modified
465    }
466    
467    #[test]
468    fn test_builder_reset() {
469        let builder = CalculatorBuilder::new()
470            .confidence_k(20.0)
471            .prior_base(60.0)
472            .reset();
473            
474        let calc = builder.build().unwrap();
475        
476        // Should have defaults after reset
477        assert_eq!(calc.confidence_k, 15.0);
478        assert_eq!(calc.prior_base, 50.0);
479        assert_eq!(calc.prior_max, 80.0);
480    }
481    
482    #[test]
483    fn test_from_config() {
484        let config = CalculatorConfig {
485            confidence_k: 22.0,
486            prior_base: 58.0,
487            prior_max: 88.0,
488        };
489        
490        let calc = CalculatorBuilder::new()
491            .from_config(config)
492            .build()
493            .unwrap();
494            
495        assert_eq!(calc.confidence_k, 22.0);
496        assert_eq!(calc.prior_base, 58.0);
497        assert_eq!(calc.prior_max, 88.0);
498    }
499    
500    #[test]
501    fn test_bonus_config_default() {
502        let bonuses = BonusConfig::default();
503        assert_eq!(bonuses.mcp_bonus_per_level, 5.0);
504        assert_eq!(bonuses.identity_bonus, 5.0);
505        assert_eq!(bonuses.security_audit_bonus, 7.0);
506        assert_eq!(bonuses.open_source_bonus, 3.0);
507        assert_eq!(bonuses.age_bonus, 5.0);
508    }
509    
510    #[test]
511    fn test_method_chaining() {
512        // Test that all methods can be chained
513        let _calc = CalculatorBuilder::new()
514            .preset(CalculatorPreset::Testing)
515            .confidence_k(10.0)
516            .prior_base(55.0)
517            .prior_max(85.0)
518            .prior_bonuses(BonusConfig::default())
519            .build()
520            .unwrap();
521    }
522}