use shape_ast::error::{Result, ShapeError};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PropertyTolerances {
pub default: f64,
pub properties: HashMap<String, f64>,
}
impl Default for PropertyTolerances {
fn default() -> Self {
let mut properties = HashMap::new();
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 {
default: 0.02,
properties,
}
}
}
impl PropertyTolerances {
pub fn get(&self, property: &str) -> f64 {
self.properties
.get(property)
.copied()
.unwrap_or(self.default)
}
pub fn set(&mut self, property: String, tolerance: f64) -> Result<()> {
if !(0.0..=1.0).contains(&tolerance) {
return Err(ShapeError::RuntimeError {
message: format!("Tolerance must be between 0.0 and 1.0, got {}", tolerance),
location: None,
});
}
self.properties.insert(property, tolerance);
Ok(())
}
pub fn from_annotation_args(args: &[shape_ast::ast::Expr]) -> Result<Self> {
let mut tolerances = Self::default();
if args.is_empty() {
return Ok(tolerances);
}
if args.len() == 1 {
if let shape_ast::ast::Expr::Literal(shape_ast::ast::Literal::Number(n), _) = &args[0] {
tolerances.default = *n;
for (_, tol) in tolerances.properties.iter_mut() {
*tol = *n;
}
return Ok(tolerances);
}
}
Ok(tolerances)
}
}
pub struct PropertyFuzzyMatcher {
tolerances: PropertyTolerances,
}
impl PropertyFuzzyMatcher {
pub fn new(tolerances: PropertyTolerances) -> Self {
Self { tolerances }
}
pub fn fuzzy_compare(&self, property: &str, actual: f64, expected: f64, op: FuzzyOp) -> bool {
let tolerance = self.tolerances.get(property);
match op {
FuzzyOp::Equal => {
let diff = (actual - expected).abs();
let avg = (actual.abs() + expected.abs()) / 2.0;
if avg == 0.0 {
diff == 0.0
} else {
diff / avg <= tolerance
}
}
FuzzyOp::Greater => actual > expected * (1.0 - tolerance),
FuzzyOp::Less => actual < expected * (1.0 + tolerance),
FuzzyOp::GreaterEq => actual >= expected * (1.0 - tolerance),
FuzzyOp::LessEq => actual <= expected * (1.0 + tolerance),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum FuzzyOp {
Equal,
Greater,
Less,
GreaterEq,
LessEq,
}