bothan_lib/registry/processor.rs
1//! Processing strategies for combining source data into signal values.
2//!
3//! This module provides processors that transform multiple input values from
4//! various sources into a single output value. Each processor implements a different
5//! strategy for combining these values, such as taking the median or weighted median.
6//!
7//! The module provides:
8//!
9//! - The [`Processor`] enum which represents different processing strategies
10//! - Specialized processor implementations in submodules
11//! - Error handling for the processing operations
12//!
13//! # Available Processors
14//!
15//! The following processing strategies are available:
16//!
17//! - [`Median`](median::MedianProcessor) - Computes the median of the input values
18//! - [`WeightedMedian`](weighted_median::WeightedMedianProcessor) - Computes a weighted median based on configurable weights
19//!
20//! # Extensibility
21//!
22//! This module is designed to be extensible. New processing strategies can be added by:
23//!
24//! 1. Creating a new processor implementation in a submodule
25//! 2. Adding a new variant to the [`Processor`] enum
26//! 3. Implementing the necessary logic in the `name` and `process` methods
27//!
28//! # Usage in Registry
29//!
30//! Processors are used within the registry system to define how source data should be
31//! combined into a single value for each signal. Each signal specifies a processor
32//! and its configuration parameters.
33
34use bincode::{Decode, Encode};
35use rust_decimal::Decimal;
36use serde::{Deserialize, Serialize};
37use thiserror::Error;
38
39pub mod median;
40pub mod weighted_median;
41
42/// Error type for processor operations.
43///
44/// This error is returned when a processor encounters an issue while
45/// processing data, such as insufficient data points or mathematical errors.
46///
47/// # Examples
48///
49/// ```
50/// use bothan_lib::registry::processor::ProcessError;
51///
52/// let error = ProcessError::new("Insufficient data points");
53/// assert_eq!(error.to_string(), "Insufficient data points");
54/// ```
55#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)]
56#[error("{msg}")]
57pub struct ProcessError {
58 msg: String,
59}
60
61impl ProcessError {
62 /// Creates a new ProcessError with the specified message.
63 pub fn new<T: Into<String>>(msg: T) -> Self {
64 ProcessError { msg: msg.into() }
65 }
66}
67
68/// Represents different strategies for processing source data into a signal value.
69///
70/// The `Processor` enum encapsulates different algorithms for combining multiple
71/// input values from various sources into a single output value. Each variant
72/// contains its own configuration parameters.
73///
74/// # Variants
75///
76/// * `Median` - Computes the median of the input values
77/// * `WeightedMedian` - Computes a weighted median based on configured weights
78///
79/// # Examples
80///
81/// Creating a median processor:
82///
83/// ```
84/// use bothan_lib::registry::processor::{Processor, median::MedianProcessor};
85/// use serde_json::json;
86///
87/// // Create a processor from JSON
88/// let json_data = json!({
89/// "function": "median",
90/// "params": {
91/// "min_source_count": 3
92/// }
93/// });
94///
95/// let processor: Processor = serde_json::from_value(json_data).unwrap();
96/// assert_eq!(processor.name(), "median");
97/// ```
98///
99/// Using a processor:
100///
101/// ```
102/// use bothan_lib::registry::processor::{Processor, median::MedianProcessor};
103/// use rust_decimal::Decimal;
104///
105/// // Create a median processor that requires at least 3 sources
106/// let processor = Processor::Median(MedianProcessor { min_source_count: 3 });
107///
108/// // Process some data
109/// let data = vec![
110/// ("source1".to_string(), Decimal::new(100, 0)),
111/// ("source2".to_string(), Decimal::new(200, 0)),
112/// ("source3".to_string(), Decimal::new(300, 0)),
113/// ];
114///
115/// let result = processor.process(data).unwrap();
116/// assert_eq!(result, Decimal::new(200, 0));
117/// ```
118#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Encode, Decode)]
119#[serde(rename_all = "snake_case", tag = "function", content = "params")]
120pub enum Processor {
121 /// Median processor that computes the median of input values.
122 Median(median::MedianProcessor),
123
124 /// Weighted median processor that applies weights to input values.
125 WeightedMedian(weighted_median::WeightedMedianProcessor),
126}
127
128impl Processor {
129 /// Returns the name of the processor.
130 ///
131 /// This method returns a string identifier for the processor type,
132 /// which can be used for logging, debugging, or serialization purposes.
133 pub fn name(&self) -> &str {
134 match self {
135 Processor::Median(_) => "median",
136 Processor::WeightedMedian(_) => "weighted_median",
137 }
138 }
139
140 /// Processes input data into a single output value.
141 ///
142 /// This method applies the processor's algorithm to the input data
143 /// to produce a single output value. The input data consists of pairs
144 /// of source identifiers and their corresponding values.
145 ///
146 /// # Errors
147 ///
148 /// Returns a `ProcessError` if the processing operation fails, such as when:
149 /// - There are insufficient data points
150 /// - The data does not meet the processor's requirements
151 /// - A mathematical error occurs during processing
152 pub fn process(&self, data: Vec<(String, Decimal)>) -> Result<Decimal, ProcessError> {
153 match self {
154 Processor::Median(median) => {
155 let data = data.into_iter().map(|(_, value)| value).collect();
156 median.process(data)
157 }
158 Processor::WeightedMedian(weighted_median) => weighted_median.process(data),
159 }
160 }
161}