shape_runtime/
fuzzy_property.rs1use shape_ast::error::{Result, ShapeError};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11pub struct PropertyTolerances {
12 pub default: f64,
14 pub properties: HashMap<String, f64>,
16}
17
18impl Default for PropertyTolerances {
19 fn default() -> Self {
20 let mut properties = HashMap::new();
21
22 properties.insert("body".to_string(), 0.02); properties.insert("upper_wick".to_string(), 0.05); properties.insert("lower_wick".to_string(), 0.05); properties.insert("range".to_string(), 0.03); properties.insert("open".to_string(), 0.01); properties.insert("close".to_string(), 0.01); properties.insert("high".to_string(), 0.01); properties.insert("low".to_string(), 0.01); properties.insert("volume".to_string(), 0.10); Self {
34 default: 0.02,
35 properties,
36 }
37 }
38}
39
40impl PropertyTolerances {
41 pub fn get(&self, property: &str) -> f64 {
43 self.properties
44 .get(property)
45 .copied()
46 .unwrap_or(self.default)
47 }
48
49 pub fn set(&mut self, property: String, tolerance: f64) -> Result<()> {
51 if !(0.0..=1.0).contains(&tolerance) {
52 return Err(ShapeError::RuntimeError {
53 message: format!("Tolerance must be between 0.0 and 1.0, got {}", tolerance),
54 location: None,
55 });
56 }
57 self.properties.insert(property, tolerance);
58 Ok(())
59 }
60
61 pub fn from_annotation_args(args: &[shape_ast::ast::Expr]) -> Result<Self> {
65 let mut tolerances = Self::default();
66
67 if args.is_empty() {
68 return Ok(tolerances);
69 }
70
71 if args.len() == 1 {
73 if let shape_ast::ast::Expr::Literal(shape_ast::ast::Literal::Number(n), _) = &args[0] {
74 tolerances.default = *n;
75 for (_, tol) in tolerances.properties.iter_mut() {
77 *tol = *n;
78 }
79 return Ok(tolerances);
80 }
81 }
82
83 Ok(tolerances)
86 }
87}
88
89pub struct PropertyFuzzyMatcher {
91 tolerances: PropertyTolerances,
92}
93
94impl PropertyFuzzyMatcher {
95 pub fn new(tolerances: PropertyTolerances) -> Self {
96 Self { tolerances }
97 }
98
99 pub fn fuzzy_compare(&self, property: &str, actual: f64, expected: f64, op: FuzzyOp) -> bool {
101 let tolerance = self.tolerances.get(property);
102
103 match op {
104 FuzzyOp::Equal => {
105 let diff = (actual - expected).abs();
106 let avg = (actual.abs() + expected.abs()) / 2.0;
107 if avg == 0.0 {
108 diff == 0.0
109 } else {
110 diff / avg <= tolerance
111 }
112 }
113 FuzzyOp::Greater => actual > expected * (1.0 - tolerance),
114 FuzzyOp::Less => actual < expected * (1.0 + tolerance),
115 FuzzyOp::GreaterEq => actual >= expected * (1.0 - tolerance),
116 FuzzyOp::LessEq => actual <= expected * (1.0 + tolerance),
117 }
118 }
119}
120
121#[derive(Debug, Clone, Copy)]
122pub enum FuzzyOp {
123 Equal,
124 Greater,
125 Less,
126 GreaterEq,
127 LessEq,
128}