Skip to main content

jmespath_extensions/
math.rs

1//! Mathematical functions.
2//!
3//! This module provides math functions for JMESPath queries.
4//!
5//! For complete function reference with signatures and examples, see the
6//! [`functions`](crate::functions) module documentation or use `jpx --list-category math`.
7//!
8//! # Example
9//!
10//! ```rust
11//! use jmespath::{Runtime, Variable};
12//! use jmespath_extensions::math;
13//!
14//! let mut runtime = Runtime::new();
15//! runtime.register_builtin_functions();
16//! math::register(&mut runtime);
17//! ```
18
19use std::collections::HashSet;
20use std::rc::Rc;
21
22use crate::common::{
23    ArgumentType, Context, ErrorReason, Function, JmespathError, Rcvar, Runtime, Variable,
24};
25use crate::define_function;
26use crate::register_if_enabled;
27
28/// Register all math functions with the runtime.
29pub fn register(runtime: &mut Runtime) {
30    runtime.register_function("round", Box::new(RoundFn::new()));
31    runtime.register_function("floor_fn", Box::new(FloorFn::new()));
32    runtime.register_function("ceil_fn", Box::new(CeilFn::new()));
33    runtime.register_function("abs_fn", Box::new(AbsFn::new()));
34    runtime.register_function("mod_fn", Box::new(ModFn::new()));
35    runtime.register_function("pow", Box::new(PowFn::new()));
36    runtime.register_function("sqrt", Box::new(SqrtFn::new()));
37    runtime.register_function("log", Box::new(LogFn::new()));
38    runtime.register_function("clamp", Box::new(ClampFn::new()));
39    runtime.register_function("median", Box::new(MedianFn::new()));
40    runtime.register_function("percentile", Box::new(PercentileFn::new()));
41    runtime.register_function("variance", Box::new(VarianceFn::new()));
42    runtime.register_function("stddev", Box::new(StddevFn::new()));
43    runtime.register_function("sin", Box::new(SinFn::new()));
44    runtime.register_function("cos", Box::new(CosFn::new()));
45    runtime.register_function("tan", Box::new(TanFn::new()));
46    runtime.register_function("asin", Box::new(AsinFn::new()));
47    runtime.register_function("acos", Box::new(AcosFn::new()));
48    runtime.register_function("atan", Box::new(AtanFn::new()));
49    runtime.register_function("atan2", Box::new(Atan2Fn::new()));
50    runtime.register_function("deg_to_rad", Box::new(DegToRadFn::new()));
51    runtime.register_function("rad_to_deg", Box::new(RadToDegFn::new()));
52    runtime.register_function("sign", Box::new(SignFn::new()));
53    runtime.register_function("add", Box::new(AddFn::new()));
54    runtime.register_function("subtract", Box::new(SubtractFn::new()));
55    runtime.register_function("multiply", Box::new(MultiplyFn::new()));
56    runtime.register_function("divide", Box::new(DivideFn::new()));
57    runtime.register_function("mode", Box::new(ModeFn::new()));
58    runtime.register_function("to_fixed", Box::new(ToFixedFn::new()));
59    runtime.register_function("format_number", Box::new(FormatNumberFn::new()));
60    runtime.register_function("histogram", Box::new(HistogramFn::new()));
61    runtime.register_function("normalize", Box::new(NormalizeFn::new()));
62    runtime.register_function("z_score", Box::new(ZScoreFn::new()));
63    runtime.register_function("correlation", Box::new(CorrelationFn::new()));
64    runtime.register_function("quantile", Box::new(QuantileFn::new()));
65    runtime.register_function("moving_avg", Box::new(MovingAvgFn::new()));
66    runtime.register_function("ewma", Box::new(EwmaFn::new()));
67    runtime.register_function("covariance", Box::new(CovarianceFn::new()));
68    runtime.register_function("standardize", Box::new(StandardizeFn::new()));
69    runtime.register_function("quartiles", Box::new(QuartilesFn::new()));
70    runtime.register_function("outliers_iqr", Box::new(OutliersIqrFn::new()));
71    runtime.register_function("outliers_zscore", Box::new(OutliersZscoreFn::new()));
72    // Time series functions
73    runtime.register_function("trend", Box::new(TrendFn::new()));
74    runtime.register_function("trend_slope", Box::new(TrendSlopeFn::new()));
75    runtime.register_function("rate_of_change", Box::new(RateOfChangeFn::new()));
76    runtime.register_function("cumulative_sum", Box::new(CumulativeSumFn::new()));
77}
78
79/// Register only the math functions that are in the enabled set.
80pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
81    register_if_enabled!(runtime, enabled, "round", Box::new(RoundFn::new()));
82    register_if_enabled!(runtime, enabled, "floor_fn", Box::new(FloorFn::new()));
83    register_if_enabled!(runtime, enabled, "ceil_fn", Box::new(CeilFn::new()));
84    register_if_enabled!(runtime, enabled, "abs_fn", Box::new(AbsFn::new()));
85    register_if_enabled!(runtime, enabled, "mod_fn", Box::new(ModFn::new()));
86    register_if_enabled!(runtime, enabled, "pow", Box::new(PowFn::new()));
87    register_if_enabled!(runtime, enabled, "sqrt", Box::new(SqrtFn::new()));
88    register_if_enabled!(runtime, enabled, "log", Box::new(LogFn::new()));
89    register_if_enabled!(runtime, enabled, "clamp", Box::new(ClampFn::new()));
90    register_if_enabled!(runtime, enabled, "median", Box::new(MedianFn::new()));
91    register_if_enabled!(
92        runtime,
93        enabled,
94        "percentile",
95        Box::new(PercentileFn::new())
96    );
97    register_if_enabled!(runtime, enabled, "variance", Box::new(VarianceFn::new()));
98    register_if_enabled!(runtime, enabled, "stddev", Box::new(StddevFn::new()));
99    register_if_enabled!(runtime, enabled, "sin", Box::new(SinFn::new()));
100    register_if_enabled!(runtime, enabled, "cos", Box::new(CosFn::new()));
101    register_if_enabled!(runtime, enabled, "tan", Box::new(TanFn::new()));
102    register_if_enabled!(runtime, enabled, "asin", Box::new(AsinFn::new()));
103    register_if_enabled!(runtime, enabled, "acos", Box::new(AcosFn::new()));
104    register_if_enabled!(runtime, enabled, "atan", Box::new(AtanFn::new()));
105    register_if_enabled!(runtime, enabled, "atan2", Box::new(Atan2Fn::new()));
106    register_if_enabled!(runtime, enabled, "deg_to_rad", Box::new(DegToRadFn::new()));
107    register_if_enabled!(runtime, enabled, "rad_to_deg", Box::new(RadToDegFn::new()));
108    register_if_enabled!(runtime, enabled, "sign", Box::new(SignFn::new()));
109    register_if_enabled!(runtime, enabled, "add", Box::new(AddFn::new()));
110    register_if_enabled!(runtime, enabled, "subtract", Box::new(SubtractFn::new()));
111    register_if_enabled!(runtime, enabled, "multiply", Box::new(MultiplyFn::new()));
112    register_if_enabled!(runtime, enabled, "divide", Box::new(DivideFn::new()));
113    register_if_enabled!(runtime, enabled, "mode", Box::new(ModeFn::new()));
114    register_if_enabled!(runtime, enabled, "to_fixed", Box::new(ToFixedFn::new()));
115    register_if_enabled!(
116        runtime,
117        enabled,
118        "format_number",
119        Box::new(FormatNumberFn::new())
120    );
121    register_if_enabled!(runtime, enabled, "histogram", Box::new(HistogramFn::new()));
122    register_if_enabled!(runtime, enabled, "normalize", Box::new(NormalizeFn::new()));
123    register_if_enabled!(runtime, enabled, "z_score", Box::new(ZScoreFn::new()));
124    register_if_enabled!(
125        runtime,
126        enabled,
127        "correlation",
128        Box::new(CorrelationFn::new())
129    );
130    register_if_enabled!(runtime, enabled, "quantile", Box::new(QuantileFn::new()));
131    register_if_enabled!(runtime, enabled, "moving_avg", Box::new(MovingAvgFn::new()));
132    register_if_enabled!(runtime, enabled, "ewma", Box::new(EwmaFn::new()));
133    register_if_enabled!(
134        runtime,
135        enabled,
136        "covariance",
137        Box::new(CovarianceFn::new())
138    );
139    register_if_enabled!(
140        runtime,
141        enabled,
142        "standardize",
143        Box::new(StandardizeFn::new())
144    );
145    register_if_enabled!(runtime, enabled, "quartiles", Box::new(QuartilesFn::new()));
146    register_if_enabled!(
147        runtime,
148        enabled,
149        "outliers_iqr",
150        Box::new(OutliersIqrFn::new())
151    );
152    register_if_enabled!(
153        runtime,
154        enabled,
155        "outliers_zscore",
156        Box::new(OutliersZscoreFn::new())
157    );
158    // Time series functions
159    register_if_enabled!(runtime, enabled, "trend", Box::new(TrendFn::new()));
160    register_if_enabled!(
161        runtime,
162        enabled,
163        "trend_slope",
164        Box::new(TrendSlopeFn::new())
165    );
166    register_if_enabled!(
167        runtime,
168        enabled,
169        "rate_of_change",
170        Box::new(RateOfChangeFn::new())
171    );
172    register_if_enabled!(
173        runtime,
174        enabled,
175        "cumulative_sum",
176        Box::new(CumulativeSumFn::new())
177    );
178}
179
180// =============================================================================
181// round(number, precision?) -> number
182// =============================================================================
183
184define_function!(
185    RoundFn,
186    vec![ArgumentType::Number],
187    Some(ArgumentType::Number)
188);
189
190impl Function for RoundFn {
191    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
192        self.signature.validate(args, ctx)?;
193
194        let n = args[0].as_number().ok_or_else(|| {
195            JmespathError::new(
196                ctx.expression,
197                0,
198                ErrorReason::Parse("Expected number argument".to_owned()),
199            )
200        })?;
201
202        let precision = if args.len() > 1 {
203            args[1].as_number().map(|p| p as i32).unwrap_or(0)
204        } else {
205            0
206        };
207
208        let result = if precision == 0 {
209            n.round()
210        } else {
211            let multiplier = 10_f64.powi(precision);
212            (n * multiplier).round() / multiplier
213        };
214
215        Ok(Rc::new(Variable::Number(
216            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
217        )))
218    }
219}
220
221// =============================================================================
222// floor_fn(number) -> number
223// =============================================================================
224
225define_function!(FloorFn, vec![ArgumentType::Number], None);
226
227impl Function for FloorFn {
228    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
229        self.signature.validate(args, ctx)?;
230
231        let n = args[0].as_number().ok_or_else(|| {
232            JmespathError::new(
233                ctx.expression,
234                0,
235                ErrorReason::Parse("Expected number argument".to_owned()),
236            )
237        })?;
238
239        Ok(Rc::new(Variable::Number(serde_json::Number::from(
240            n.floor() as i64,
241        ))))
242    }
243}
244
245// =============================================================================
246// ceil_fn(number) -> number
247// =============================================================================
248
249define_function!(CeilFn, vec![ArgumentType::Number], None);
250
251impl Function for CeilFn {
252    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
253        self.signature.validate(args, ctx)?;
254
255        let n = args[0].as_number().ok_or_else(|| {
256            JmespathError::new(
257                ctx.expression,
258                0,
259                ErrorReason::Parse("Expected number argument".to_owned()),
260            )
261        })?;
262
263        Ok(Rc::new(Variable::Number(serde_json::Number::from(
264            n.ceil() as i64,
265        ))))
266    }
267}
268
269// =============================================================================
270// abs_fn(number) -> number
271// =============================================================================
272
273define_function!(AbsFn, vec![ArgumentType::Number], None);
274
275impl Function for AbsFn {
276    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
277        self.signature.validate(args, ctx)?;
278
279        let n = args[0].as_number().ok_or_else(|| {
280            JmespathError::new(
281                ctx.expression,
282                0,
283                ErrorReason::Parse("Expected number argument".to_owned()),
284            )
285        })?;
286
287        Ok(Rc::new(Variable::Number(
288            serde_json::Number::from_f64(n.abs()).unwrap_or_else(|| serde_json::Number::from(0)),
289        )))
290    }
291}
292
293// =============================================================================
294// mod_fn(number, divisor) -> number
295// =============================================================================
296
297define_function!(
298    ModFn,
299    vec![ArgumentType::Number, ArgumentType::Number],
300    None
301);
302
303impl Function for ModFn {
304    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
305        self.signature.validate(args, ctx)?;
306
307        let n = args[0].as_number().ok_or_else(|| {
308            JmespathError::new(
309                ctx.expression,
310                0,
311                ErrorReason::Parse("Expected number argument".to_owned()),
312            )
313        })?;
314
315        let divisor = args[1].as_number().ok_or_else(|| {
316            JmespathError::new(
317                ctx.expression,
318                0,
319                ErrorReason::Parse("Expected divisor argument".to_owned()),
320            )
321        })?;
322
323        if divisor == 0.0 {
324            return Err(JmespathError::new(
325                ctx.expression,
326                0,
327                ErrorReason::Parse("Division by zero".to_owned()),
328            ));
329        }
330
331        Ok(Rc::new(Variable::Number(
332            serde_json::Number::from_f64(n % divisor)
333                .unwrap_or_else(|| serde_json::Number::from(0)),
334        )))
335    }
336}
337
338// =============================================================================
339// pow(base, exponent) -> number
340// =============================================================================
341
342define_function!(
343    PowFn,
344    vec![ArgumentType::Number, ArgumentType::Number],
345    None
346);
347
348impl Function for PowFn {
349    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
350        self.signature.validate(args, ctx)?;
351
352        let base = args[0].as_number().ok_or_else(|| {
353            JmespathError::new(
354                ctx.expression,
355                0,
356                ErrorReason::Parse("Expected base number".to_owned()),
357            )
358        })?;
359
360        let exp = args[1].as_number().ok_or_else(|| {
361            JmespathError::new(
362                ctx.expression,
363                0,
364                ErrorReason::Parse("Expected exponent number".to_owned()),
365            )
366        })?;
367
368        Ok(Rc::new(Variable::Number(
369            serde_json::Number::from_f64(base.powf(exp))
370                .unwrap_or_else(|| serde_json::Number::from(0)),
371        )))
372    }
373}
374
375// =============================================================================
376// sqrt(number) -> number
377// =============================================================================
378
379define_function!(SqrtFn, vec![ArgumentType::Number], None);
380
381impl Function for SqrtFn {
382    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
383        self.signature.validate(args, ctx)?;
384
385        let n = args[0].as_number().ok_or_else(|| {
386            JmespathError::new(
387                ctx.expression,
388                0,
389                ErrorReason::Parse("Expected number argument".to_owned()),
390            )
391        })?;
392
393        if n < 0.0 {
394            return Err(JmespathError::new(
395                ctx.expression,
396                0,
397                ErrorReason::Parse("Cannot take square root of negative number".to_owned()),
398            ));
399        }
400
401        Ok(Rc::new(Variable::Number(
402            serde_json::Number::from_f64(n.sqrt()).unwrap_or_else(|| serde_json::Number::from(0)),
403        )))
404    }
405}
406
407// =============================================================================
408// log(number, base?) -> number (default base e)
409// =============================================================================
410
411define_function!(
412    LogFn,
413    vec![ArgumentType::Number],
414    Some(ArgumentType::Number)
415);
416
417impl Function for LogFn {
418    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
419        self.signature.validate(args, ctx)?;
420
421        let n = args[0].as_number().ok_or_else(|| {
422            JmespathError::new(
423                ctx.expression,
424                0,
425                ErrorReason::Parse("Expected number argument".to_owned()),
426            )
427        })?;
428
429        if n <= 0.0 {
430            return Err(JmespathError::new(
431                ctx.expression,
432                0,
433                ErrorReason::Parse("Logarithm requires positive number".to_owned()),
434            ));
435        }
436
437        let result = if args.len() > 1 {
438            let base = args[1].as_number().ok_or_else(|| {
439                JmespathError::new(
440                    ctx.expression,
441                    0,
442                    ErrorReason::Parse("Expected base number".to_owned()),
443                )
444            })?;
445            n.log(base)
446        } else {
447            n.ln()
448        };
449
450        Ok(Rc::new(Variable::Number(
451            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
452        )))
453    }
454}
455
456// =============================================================================
457// clamp(number, min, max) -> number
458// =============================================================================
459
460define_function!(
461    ClampFn,
462    vec![
463        ArgumentType::Number,
464        ArgumentType::Number,
465        ArgumentType::Number
466    ],
467    None
468);
469
470impl Function for ClampFn {
471    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
472        self.signature.validate(args, ctx)?;
473
474        let n = args[0].as_number().ok_or_else(|| {
475            JmespathError::new(
476                ctx.expression,
477                0,
478                ErrorReason::Parse("Expected number argument".to_owned()),
479            )
480        })?;
481
482        let min = args[1].as_number().ok_or_else(|| {
483            JmespathError::new(
484                ctx.expression,
485                0,
486                ErrorReason::Parse("Expected min number".to_owned()),
487            )
488        })?;
489
490        let max = args[2].as_number().ok_or_else(|| {
491            JmespathError::new(
492                ctx.expression,
493                0,
494                ErrorReason::Parse("Expected max number".to_owned()),
495            )
496        })?;
497
498        let result = n.max(min).min(max);
499
500        Ok(Rc::new(Variable::Number(
501            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
502        )))
503    }
504}
505
506// =============================================================================
507// median(array) -> number
508// =============================================================================
509
510define_function!(MedianFn, vec![ArgumentType::Array], None);
511
512impl Function for MedianFn {
513    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
514        self.signature.validate(args, ctx)?;
515
516        let arr = args[0].as_array().ok_or_else(|| {
517            JmespathError::new(
518                ctx.expression,
519                0,
520                ErrorReason::Parse("Expected array argument".to_owned()),
521            )
522        })?;
523
524        let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
525
526        if numbers.is_empty() {
527            return Ok(Rc::new(Variable::Null));
528        }
529
530        numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
531
532        let len = numbers.len();
533        let median = if len % 2 == 0 {
534            (numbers[len / 2 - 1] + numbers[len / 2]) / 2.0
535        } else {
536            numbers[len / 2]
537        };
538
539        Ok(Rc::new(Variable::Number(
540            serde_json::Number::from_f64(median).unwrap_or_else(|| serde_json::Number::from(0)),
541        )))
542    }
543}
544
545// =============================================================================
546// percentile(array, p) -> number (pth percentile, p in 0-100)
547// =============================================================================
548
549define_function!(
550    PercentileFn,
551    vec![ArgumentType::Array, ArgumentType::Number],
552    None
553);
554
555impl Function for PercentileFn {
556    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
557        self.signature.validate(args, ctx)?;
558
559        let arr = args[0].as_array().ok_or_else(|| {
560            JmespathError::new(
561                ctx.expression,
562                0,
563                ErrorReason::Parse("Expected array argument".to_owned()),
564            )
565        })?;
566
567        let p = args[1].as_number().ok_or_else(|| {
568            JmespathError::new(
569                ctx.expression,
570                0,
571                ErrorReason::Parse("Expected percentile value".to_owned()),
572            )
573        })?;
574
575        if !(0.0..=100.0).contains(&p) {
576            return Err(JmespathError::new(
577                ctx.expression,
578                0,
579                ErrorReason::Parse("Percentile must be between 0 and 100".to_owned()),
580            ));
581        }
582
583        let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
584
585        if numbers.is_empty() {
586            return Ok(Rc::new(Variable::Null));
587        }
588
589        numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
590
591        let len = numbers.len();
592        if len == 1 {
593            return Ok(Rc::new(Variable::Number(
594                serde_json::Number::from_f64(numbers[0])
595                    .unwrap_or_else(|| serde_json::Number::from(0)),
596            )));
597        }
598
599        let rank = (p / 100.0) * (len - 1) as f64;
600        let lower_idx = rank.floor() as usize;
601        let upper_idx = rank.ceil() as usize;
602        let fraction = rank - lower_idx as f64;
603
604        let result = if lower_idx == upper_idx {
605            numbers[lower_idx]
606        } else {
607            numbers[lower_idx] * (1.0 - fraction) + numbers[upper_idx] * fraction
608        };
609
610        Ok(Rc::new(Variable::Number(
611            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
612        )))
613    }
614}
615
616// =============================================================================
617// variance(array) -> number (population variance)
618// =============================================================================
619
620define_function!(VarianceFn, vec![ArgumentType::Array], None);
621
622impl Function for VarianceFn {
623    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
624        self.signature.validate(args, ctx)?;
625
626        let arr = args[0].as_array().ok_or_else(|| {
627            JmespathError::new(
628                ctx.expression,
629                0,
630                ErrorReason::Parse("Expected array argument".to_owned()),
631            )
632        })?;
633
634        let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
635
636        if numbers.is_empty() {
637            return Ok(Rc::new(Variable::Null));
638        }
639
640        let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
641        let variance =
642            numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
643
644        Ok(Rc::new(Variable::Number(
645            serde_json::Number::from_f64(variance).unwrap_or_else(|| serde_json::Number::from(0)),
646        )))
647    }
648}
649
650// =============================================================================
651// stddev(array) -> number (population standard deviation)
652// =============================================================================
653
654define_function!(StddevFn, vec![ArgumentType::Array], None);
655
656impl Function for StddevFn {
657    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
658        self.signature.validate(args, ctx)?;
659
660        let arr = args[0].as_array().ok_or_else(|| {
661            JmespathError::new(
662                ctx.expression,
663                0,
664                ErrorReason::Parse("Expected array argument".to_owned()),
665            )
666        })?;
667
668        let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
669
670        if numbers.is_empty() {
671            return Ok(Rc::new(Variable::Null));
672        }
673
674        let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
675        let variance =
676            numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
677        let stddev = variance.sqrt();
678
679        Ok(Rc::new(Variable::Number(
680            serde_json::Number::from_f64(stddev).unwrap_or_else(|| serde_json::Number::from(0)),
681        )))
682    }
683}
684
685// =============================================================================
686// Trigonometric functions
687// =============================================================================
688
689define_function!(SinFn, vec![ArgumentType::Number], None);
690
691impl Function for SinFn {
692    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
693        self.signature.validate(args, ctx)?;
694        let n = args[0].as_number().ok_or_else(|| {
695            JmespathError::new(
696                ctx.expression,
697                0,
698                ErrorReason::Parse("Expected number".to_owned()),
699            )
700        })?;
701        Ok(Rc::new(Variable::Number(
702            serde_json::Number::from_f64(n.sin()).unwrap_or_else(|| serde_json::Number::from(0)),
703        )))
704    }
705}
706
707define_function!(CosFn, vec![ArgumentType::Number], None);
708
709impl Function for CosFn {
710    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
711        self.signature.validate(args, ctx)?;
712        let n = args[0].as_number().ok_or_else(|| {
713            JmespathError::new(
714                ctx.expression,
715                0,
716                ErrorReason::Parse("Expected number".to_owned()),
717            )
718        })?;
719        Ok(Rc::new(Variable::Number(
720            serde_json::Number::from_f64(n.cos()).unwrap_or_else(|| serde_json::Number::from(0)),
721        )))
722    }
723}
724
725define_function!(TanFn, vec![ArgumentType::Number], None);
726
727impl Function for TanFn {
728    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
729        self.signature.validate(args, ctx)?;
730        let n = args[0].as_number().ok_or_else(|| {
731            JmespathError::new(
732                ctx.expression,
733                0,
734                ErrorReason::Parse("Expected number".to_owned()),
735            )
736        })?;
737        Ok(Rc::new(Variable::Number(
738            serde_json::Number::from_f64(n.tan()).unwrap_or_else(|| serde_json::Number::from(0)),
739        )))
740    }
741}
742
743define_function!(AsinFn, vec![ArgumentType::Number], None);
744
745impl Function for AsinFn {
746    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
747        self.signature.validate(args, ctx)?;
748        let n = args[0].as_number().ok_or_else(|| {
749            JmespathError::new(
750                ctx.expression,
751                0,
752                ErrorReason::Parse("Expected number".to_owned()),
753            )
754        })?;
755        let result = n.asin();
756        // Return null for out-of-domain values (|n| > 1 produces NaN)
757        if result.is_nan() {
758            Ok(Rc::new(Variable::Null))
759        } else {
760            Ok(Rc::new(Variable::Number(
761                serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
762            )))
763        }
764    }
765}
766
767define_function!(AcosFn, vec![ArgumentType::Number], None);
768
769impl Function for AcosFn {
770    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
771        self.signature.validate(args, ctx)?;
772        let n = args[0].as_number().ok_or_else(|| {
773            JmespathError::new(
774                ctx.expression,
775                0,
776                ErrorReason::Parse("Expected number".to_owned()),
777            )
778        })?;
779        let result = n.acos();
780        // Return null for out-of-domain values (|n| > 1 produces NaN)
781        if result.is_nan() {
782            Ok(Rc::new(Variable::Null))
783        } else {
784            Ok(Rc::new(Variable::Number(
785                serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
786            )))
787        }
788    }
789}
790
791define_function!(AtanFn, vec![ArgumentType::Number], None);
792
793impl Function for AtanFn {
794    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
795        self.signature.validate(args, ctx)?;
796        let n = args[0].as_number().ok_or_else(|| {
797            JmespathError::new(
798                ctx.expression,
799                0,
800                ErrorReason::Parse("Expected number".to_owned()),
801            )
802        })?;
803        Ok(Rc::new(Variable::Number(
804            serde_json::Number::from_f64(n.atan()).unwrap_or_else(|| serde_json::Number::from(0)),
805        )))
806    }
807}
808
809define_function!(
810    Atan2Fn,
811    vec![ArgumentType::Number, ArgumentType::Number],
812    None
813);
814
815impl Function for Atan2Fn {
816    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
817        self.signature.validate(args, ctx)?;
818        let y = args[0].as_number().ok_or_else(|| {
819            JmespathError::new(
820                ctx.expression,
821                0,
822                ErrorReason::Parse("Expected number".to_owned()),
823            )
824        })?;
825        let x = args[1].as_number().ok_or_else(|| {
826            JmespathError::new(
827                ctx.expression,
828                0,
829                ErrorReason::Parse("Expected number".to_owned()),
830            )
831        })?;
832        Ok(Rc::new(Variable::Number(
833            serde_json::Number::from_f64(y.atan2(x)).unwrap_or_else(|| serde_json::Number::from(0)),
834        )))
835    }
836}
837
838define_function!(DegToRadFn, vec![ArgumentType::Number], None);
839
840impl Function for DegToRadFn {
841    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
842        self.signature.validate(args, ctx)?;
843        let n = args[0].as_number().ok_or_else(|| {
844            JmespathError::new(
845                ctx.expression,
846                0,
847                ErrorReason::Parse("Expected number".to_owned()),
848            )
849        })?;
850        Ok(Rc::new(Variable::Number(
851            serde_json::Number::from_f64(n.to_radians())
852                .unwrap_or_else(|| serde_json::Number::from(0)),
853        )))
854    }
855}
856
857define_function!(RadToDegFn, vec![ArgumentType::Number], None);
858
859impl Function for RadToDegFn {
860    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
861        self.signature.validate(args, ctx)?;
862        let n = args[0].as_number().ok_or_else(|| {
863            JmespathError::new(
864                ctx.expression,
865                0,
866                ErrorReason::Parse("Expected number".to_owned()),
867            )
868        })?;
869        Ok(Rc::new(Variable::Number(
870            serde_json::Number::from_f64(n.to_degrees())
871                .unwrap_or_else(|| serde_json::Number::from(0)),
872        )))
873    }
874}
875
876define_function!(SignFn, vec![ArgumentType::Number], None);
877
878impl Function for SignFn {
879    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
880        self.signature.validate(args, ctx)?;
881        let n = args[0].as_number().ok_or_else(|| {
882            JmespathError::new(
883                ctx.expression,
884                0,
885                ErrorReason::Parse("Expected number".to_owned()),
886            )
887        })?;
888        let sign = if n > 0.0 {
889            1
890        } else if n < 0.0 {
891            -1
892        } else {
893            0
894        };
895        Ok(Rc::new(Variable::Number(serde_json::Number::from(sign))))
896    }
897}
898
899// =============================================================================
900// add(a, b) -> number
901// =============================================================================
902
903define_function!(
904    AddFn,
905    vec![ArgumentType::Number, ArgumentType::Number],
906    None
907);
908
909impl Function for AddFn {
910    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
911        self.signature.validate(args, ctx)?;
912        let a = args[0].as_number().ok_or_else(|| {
913            JmespathError::new(
914                ctx.expression,
915                0,
916                ErrorReason::Parse("Expected number".to_owned()),
917            )
918        })?;
919        let b = args[1].as_number().ok_or_else(|| {
920            JmespathError::new(
921                ctx.expression,
922                0,
923                ErrorReason::Parse("Expected number".to_owned()),
924            )
925        })?;
926        Ok(Rc::new(Variable::Number(
927            serde_json::Number::from_f64(a + b).unwrap_or_else(|| serde_json::Number::from(0)),
928        )))
929    }
930}
931
932// =============================================================================
933// subtract(a, b) -> number
934// =============================================================================
935
936define_function!(
937    SubtractFn,
938    vec![ArgumentType::Number, ArgumentType::Number],
939    None
940);
941
942impl Function for SubtractFn {
943    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
944        self.signature.validate(args, ctx)?;
945        let a = args[0].as_number().ok_or_else(|| {
946            JmespathError::new(
947                ctx.expression,
948                0,
949                ErrorReason::Parse("Expected number".to_owned()),
950            )
951        })?;
952        let b = args[1].as_number().ok_or_else(|| {
953            JmespathError::new(
954                ctx.expression,
955                0,
956                ErrorReason::Parse("Expected number".to_owned()),
957            )
958        })?;
959        Ok(Rc::new(Variable::Number(
960            serde_json::Number::from_f64(a - b).unwrap_or_else(|| serde_json::Number::from(0)),
961        )))
962    }
963}
964
965// =============================================================================
966// multiply(a, b) -> number
967// =============================================================================
968
969define_function!(
970    MultiplyFn,
971    vec![ArgumentType::Number, ArgumentType::Number],
972    None
973);
974
975impl Function for MultiplyFn {
976    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
977        self.signature.validate(args, ctx)?;
978        let a = args[0].as_number().ok_or_else(|| {
979            JmespathError::new(
980                ctx.expression,
981                0,
982                ErrorReason::Parse("Expected number".to_owned()),
983            )
984        })?;
985        let b = args[1].as_number().ok_or_else(|| {
986            JmespathError::new(
987                ctx.expression,
988                0,
989                ErrorReason::Parse("Expected number".to_owned()),
990            )
991        })?;
992        Ok(Rc::new(Variable::Number(
993            serde_json::Number::from_f64(a * b).unwrap_or_else(|| serde_json::Number::from(0)),
994        )))
995    }
996}
997
998// =============================================================================
999// divide(a, b) -> number
1000// =============================================================================
1001
1002define_function!(
1003    DivideFn,
1004    vec![ArgumentType::Number, ArgumentType::Number],
1005    None
1006);
1007
1008impl Function for DivideFn {
1009    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1010        self.signature.validate(args, ctx)?;
1011        let a = args[0].as_number().ok_or_else(|| {
1012            JmespathError::new(
1013                ctx.expression,
1014                0,
1015                ErrorReason::Parse("Expected number".to_owned()),
1016            )
1017        })?;
1018        let b = args[1].as_number().ok_or_else(|| {
1019            JmespathError::new(
1020                ctx.expression,
1021                0,
1022                ErrorReason::Parse("Expected number".to_owned()),
1023            )
1024        })?;
1025        if b == 0.0 {
1026            return Err(JmespathError::new(
1027                ctx.expression,
1028                0,
1029                ErrorReason::Parse("Division by zero".to_owned()),
1030            ));
1031        }
1032        Ok(Rc::new(Variable::Number(
1033            serde_json::Number::from_f64(a / b).unwrap_or_else(|| serde_json::Number::from(0)),
1034        )))
1035    }
1036}
1037
1038// =============================================================================
1039// mode(array) -> any (most common value)
1040// =============================================================================
1041
1042define_function!(ModeFn, vec![ArgumentType::Array], None);
1043
1044impl Function for ModeFn {
1045    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1046        self.signature.validate(args, ctx)?;
1047
1048        let arr = args[0].as_array().ok_or_else(|| {
1049            JmespathError::new(
1050                ctx.expression,
1051                0,
1052                ErrorReason::Parse("Expected array argument".to_owned()),
1053            )
1054        })?;
1055
1056        if arr.is_empty() {
1057            return Ok(Rc::new(Variable::Null));
1058        }
1059
1060        // Count occurrences - use JSON string representation as key
1061        let mut counts: std::collections::HashMap<String, (usize, Rcvar)> =
1062            std::collections::HashMap::new();
1063
1064        for item in arr.iter() {
1065            let key = serde_json::to_string(&**item).unwrap_or_default();
1066            counts
1067                .entry(key)
1068                .and_modify(|(count, _)| *count += 1)
1069                .or_insert((1, item.clone()));
1070        }
1071
1072        // Find the value with highest count
1073        let (_, (_, mode_value)) = counts
1074            .into_iter()
1075            .max_by_key(|(_, (count, _))| *count)
1076            .unwrap();
1077
1078        Ok(mode_value)
1079    }
1080}
1081
1082// =============================================================================
1083// to_fixed(number, precision) -> string
1084// Like JavaScript's Number.toFixed() - returns string with exact decimal places
1085// =============================================================================
1086
1087define_function!(
1088    ToFixedFn,
1089    vec![ArgumentType::Number, ArgumentType::Number],
1090    None
1091);
1092
1093impl Function for ToFixedFn {
1094    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1095        self.signature.validate(args, ctx)?;
1096
1097        let num = args[0].as_number().ok_or_else(|| {
1098            JmespathError::new(
1099                ctx.expression,
1100                0,
1101                ErrorReason::Parse("Expected number argument".to_owned()),
1102            )
1103        })?;
1104
1105        let precision = args[1].as_number().ok_or_else(|| {
1106            JmespathError::new(
1107                ctx.expression,
1108                0,
1109                ErrorReason::Parse("Expected precision argument".to_owned()),
1110            )
1111        })? as usize;
1112
1113        let result = format!("{:.prec$}", num, prec = precision);
1114        Ok(Rc::new(Variable::String(result)))
1115    }
1116}
1117
1118// =============================================================================
1119// format_number(number, precision?, suffix?) -> string
1120// Format a number with thousand separators and optional suffix (k, M, B, etc.)
1121// =============================================================================
1122
1123define_function!(
1124    FormatNumberFn,
1125    vec![ArgumentType::Number],
1126    Some(ArgumentType::Any)
1127);
1128
1129impl Function for FormatNumberFn {
1130    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1131        self.signature.validate(args, ctx)?;
1132
1133        let num = args[0].as_number().ok_or_else(|| {
1134            JmespathError::new(
1135                ctx.expression,
1136                0,
1137                ErrorReason::Parse("Expected number argument".to_owned()),
1138            )
1139        })?;
1140
1141        let precision = args
1142            .get(1)
1143            .and_then(|v| v.as_number())
1144            .map(|n| n as usize)
1145            .unwrap_or(0);
1146
1147        let suffix = args
1148            .get(2)
1149            .and_then(|v| v.as_string())
1150            .map(|s| s.to_string());
1151
1152        // Handle suffix scaling (k, M, B, T)
1153        let (scaled_num, auto_suffix) = if let Some(ref s) = suffix {
1154            match s.as_str() {
1155                "k" | "K" => (num / 1_000.0, "k"),
1156                "M" => (num / 1_000_000.0, "M"),
1157                "B" => (num / 1_000_000_000.0, "B"),
1158                "T" => (num / 1_000_000_000_000.0, "T"),
1159                "auto" => {
1160                    // Auto-detect best suffix
1161                    let abs_num = num.abs();
1162                    if abs_num >= 1_000_000_000_000.0 {
1163                        (num / 1_000_000_000_000.0, "T")
1164                    } else if abs_num >= 1_000_000_000.0 {
1165                        (num / 1_000_000_000.0, "B")
1166                    } else if abs_num >= 1_000_000.0 {
1167                        (num / 1_000_000.0, "M")
1168                    } else if abs_num >= 1_000.0 {
1169                        (num / 1_000.0, "k")
1170                    } else {
1171                        (num, "")
1172                    }
1173                }
1174                _ => (num, s.as_str()),
1175            }
1176        } else {
1177            (num, "")
1178        };
1179
1180        // Format with precision
1181        let formatted = format!("{:.prec$}", scaled_num, prec = precision);
1182
1183        // Add thousand separators to the integer part
1184        let result = if suffix.is_none() || suffix.as_deref() == Some("") {
1185            add_thousand_separators(&formatted)
1186        } else {
1187            format!("{}{}", formatted, auto_suffix)
1188        };
1189
1190        Ok(Rc::new(Variable::String(result)))
1191    }
1192}
1193
1194/// Add thousand separators (commas) to a number string
1195fn add_thousand_separators(s: &str) -> String {
1196    let parts: Vec<&str> = s.split('.').collect();
1197    let int_part = parts[0];
1198    let dec_part = parts.get(1);
1199
1200    // Handle negative sign
1201    let (sign, digits) = if let Some(stripped) = int_part.strip_prefix('-') {
1202        ("-", stripped)
1203    } else {
1204        ("", int_part)
1205    };
1206
1207    // Add commas every 3 digits from the right
1208    let digit_chars: Vec<char> = digits.chars().collect();
1209    let len = digit_chars.len();
1210    let with_commas: String = digit_chars
1211        .iter()
1212        .enumerate()
1213        .map(|(i, c)| {
1214            let pos_from_right = len - 1 - i;
1215            if pos_from_right > 0 && pos_from_right % 3 == 0 {
1216                format!("{},", c)
1217            } else {
1218                c.to_string()
1219            }
1220        })
1221        .collect();
1222
1223    match dec_part {
1224        Some(dec) => format!("{}{}.{}", sign, with_commas, dec),
1225        None => format!("{}{}", sign, with_commas),
1226    }
1227}
1228
1229// =============================================================================
1230// histogram(array, bins) -> array
1231// Bucket values into histogram bins
1232// =============================================================================
1233
1234define_function!(
1235    HistogramFn,
1236    vec![ArgumentType::Array, ArgumentType::Number],
1237    None
1238);
1239
1240impl Function for HistogramFn {
1241    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1242        self.signature.validate(args, ctx)?;
1243
1244        let arr = args[0].as_array().ok_or_else(|| {
1245            JmespathError::new(
1246                ctx.expression,
1247                0,
1248                ErrorReason::Parse("Expected array argument".to_owned()),
1249            )
1250        })?;
1251
1252        let num_bins = args[1].as_number().ok_or_else(|| {
1253            JmespathError::new(
1254                ctx.expression,
1255                0,
1256                ErrorReason::Parse("Expected number of bins".to_owned()),
1257            )
1258        })? as usize;
1259
1260        if num_bins == 0 {
1261            return Err(JmespathError::new(
1262                ctx.expression,
1263                0,
1264                ErrorReason::Parse("Number of bins must be greater than 0".to_owned()),
1265            ));
1266        }
1267
1268        // Extract numeric values
1269        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1270
1271        if values.is_empty() {
1272            return Ok(Rc::new(Variable::Array(vec![])));
1273        }
1274
1275        let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
1276        let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1277
1278        // Handle case where all values are the same
1279        let bin_width = if (max_val - min_val).abs() < f64::EPSILON {
1280            1.0
1281        } else {
1282            (max_val - min_val) / num_bins as f64
1283        };
1284
1285        // Initialize bins
1286        let mut bins: Vec<(f64, f64, usize)> = (0..num_bins)
1287            .map(|i| {
1288                let bin_min = min_val + (i as f64 * bin_width);
1289                let bin_max = if i == num_bins - 1 {
1290                    max_val
1291                } else {
1292                    min_val + ((i + 1) as f64 * bin_width)
1293                };
1294                (bin_min, bin_max, 0)
1295            })
1296            .collect();
1297
1298        // Count values in each bin
1299        for val in &values {
1300            let bin_idx = if (max_val - min_val).abs() < f64::EPSILON {
1301                0
1302            } else {
1303                let idx = ((val - min_val) / bin_width) as usize;
1304                idx.min(num_bins - 1)
1305            };
1306            bins[bin_idx].2 += 1;
1307        }
1308
1309        // Convert to array of objects
1310        let result: Vec<Rcvar> = bins
1311            .into_iter()
1312            .map(|(bin_min, bin_max, count)| {
1313                let mut map = std::collections::BTreeMap::new();
1314                map.insert(
1315                    "min".to_string(),
1316                    Rc::new(Variable::Number(
1317                        serde_json::Number::from_f64(bin_min)
1318                            .unwrap_or_else(|| serde_json::Number::from(0)),
1319                    )) as Rcvar,
1320                );
1321                map.insert(
1322                    "max".to_string(),
1323                    Rc::new(Variable::Number(
1324                        serde_json::Number::from_f64(bin_max)
1325                            .unwrap_or_else(|| serde_json::Number::from(0)),
1326                    )) as Rcvar,
1327                );
1328                map.insert(
1329                    "count".to_string(),
1330                    Rc::new(Variable::Number(serde_json::Number::from(count))) as Rcvar,
1331                );
1332                Rc::new(Variable::Object(map)) as Rcvar
1333            })
1334            .collect();
1335
1336        Ok(Rc::new(Variable::Array(result)))
1337    }
1338}
1339
1340// =============================================================================
1341// normalize(array) -> array
1342// Normalize values to 0-1 range
1343// =============================================================================
1344
1345define_function!(NormalizeFn, vec![ArgumentType::Array], None);
1346
1347impl Function for NormalizeFn {
1348    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1349        self.signature.validate(args, ctx)?;
1350
1351        let arr = args[0].as_array().ok_or_else(|| {
1352            JmespathError::new(
1353                ctx.expression,
1354                0,
1355                ErrorReason::Parse("Expected array argument".to_owned()),
1356            )
1357        })?;
1358
1359        // Extract numeric values
1360        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1361
1362        if values.is_empty() {
1363            return Ok(Rc::new(Variable::Array(vec![])));
1364        }
1365
1366        let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
1367        let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1368        let range = max_val - min_val;
1369
1370        let result: Vec<Rcvar> = values
1371            .iter()
1372            .map(|v| {
1373                let normalized = if range.abs() < f64::EPSILON {
1374                    0.0 // All values are the same
1375                } else {
1376                    (v - min_val) / range
1377                };
1378                Rc::new(Variable::Number(
1379                    serde_json::Number::from_f64(normalized)
1380                        .unwrap_or_else(|| serde_json::Number::from(0)),
1381                )) as Rcvar
1382            })
1383            .collect();
1384
1385        Ok(Rc::new(Variable::Array(result)))
1386    }
1387}
1388
1389// =============================================================================
1390// z_score(array) -> array
1391// Calculate z-scores (standard scores) for values
1392// =============================================================================
1393
1394define_function!(ZScoreFn, vec![ArgumentType::Array], None);
1395
1396impl Function for ZScoreFn {
1397    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1398        self.signature.validate(args, ctx)?;
1399
1400        let arr = args[0].as_array().ok_or_else(|| {
1401            JmespathError::new(
1402                ctx.expression,
1403                0,
1404                ErrorReason::Parse("Expected array argument".to_owned()),
1405            )
1406        })?;
1407
1408        // Extract numeric values
1409        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1410
1411        if values.is_empty() {
1412            return Ok(Rc::new(Variable::Array(vec![])));
1413        }
1414
1415        let n = values.len() as f64;
1416        let mean = values.iter().sum::<f64>() / n;
1417        let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
1418        let stddev = variance.sqrt();
1419
1420        let result: Vec<Rcvar> = values
1421            .iter()
1422            .map(|v| {
1423                let z = if stddev.abs() < f64::EPSILON {
1424                    0.0 // All values are the same
1425                } else {
1426                    (v - mean) / stddev
1427                };
1428                Rc::new(Variable::Number(
1429                    serde_json::Number::from_f64(z).unwrap_or_else(|| serde_json::Number::from(0)),
1430                )) as Rcvar
1431            })
1432            .collect();
1433
1434        Ok(Rc::new(Variable::Array(result)))
1435    }
1436}
1437
1438// =============================================================================
1439// correlation(arr1, arr2) -> number
1440// Pearson correlation coefficient between two arrays
1441// =============================================================================
1442
1443define_function!(
1444    CorrelationFn,
1445    vec![ArgumentType::Array, ArgumentType::Array],
1446    None
1447);
1448
1449impl Function for CorrelationFn {
1450    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1451        self.signature.validate(args, ctx)?;
1452
1453        let arr1 = args[0].as_array().ok_or_else(|| {
1454            JmespathError::new(
1455                ctx.expression,
1456                0,
1457                ErrorReason::Parse("Expected array argument".to_owned()),
1458            )
1459        })?;
1460
1461        let arr2 = args[1].as_array().ok_or_else(|| {
1462            JmespathError::new(
1463                ctx.expression,
1464                0,
1465                ErrorReason::Parse("Expected array argument".to_owned()),
1466            )
1467        })?;
1468
1469        // Extract numeric values
1470        let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_number()).collect();
1471        let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_number()).collect();
1472
1473        if values1.is_empty() || values2.is_empty() {
1474            return Ok(Rc::new(Variable::Null));
1475        }
1476
1477        // Use the shorter length
1478        let n = values1.len().min(values2.len());
1479        if n == 0 {
1480            return Ok(Rc::new(Variable::Null));
1481        }
1482
1483        let values1 = &values1[..n];
1484        let values2 = &values2[..n];
1485
1486        let mean1 = values1.iter().sum::<f64>() / n as f64;
1487        let mean2 = values2.iter().sum::<f64>() / n as f64;
1488
1489        let mut cov = 0.0;
1490        let mut var1 = 0.0;
1491        let mut var2 = 0.0;
1492
1493        for i in 0..n {
1494            let d1 = values1[i] - mean1;
1495            let d2 = values2[i] - mean2;
1496            cov += d1 * d2;
1497            var1 += d1 * d1;
1498            var2 += d2 * d2;
1499        }
1500
1501        let denom = (var1 * var2).sqrt();
1502        let correlation = if denom.abs() < f64::EPSILON {
1503            0.0 // No variance in one or both arrays
1504        } else {
1505            cov / denom
1506        };
1507
1508        Ok(Rc::new(Variable::Number(
1509            serde_json::Number::from_f64(correlation)
1510                .unwrap_or_else(|| serde_json::Number::from(0)),
1511        )))
1512    }
1513}
1514
1515// =============================================================================
1516// quantile(array, q) -> number
1517// Nth quantile (generalized percentile), q in [0, 1]
1518// =============================================================================
1519
1520define_function!(
1521    QuantileFn,
1522    vec![ArgumentType::Array, ArgumentType::Number],
1523    None
1524);
1525
1526impl Function for QuantileFn {
1527    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1528        self.signature.validate(args, ctx)?;
1529
1530        let arr = args[0].as_array().ok_or_else(|| {
1531            JmespathError::new(
1532                ctx.expression,
1533                0,
1534                ErrorReason::Parse("Expected array argument".to_owned()),
1535            )
1536        })?;
1537
1538        let q = args[1].as_number().ok_or_else(|| {
1539            JmespathError::new(
1540                ctx.expression,
1541                0,
1542                ErrorReason::Parse("Expected number for quantile".to_owned()),
1543            )
1544        })?;
1545
1546        if !(0.0..=1.0).contains(&q) {
1547            return Ok(Rc::new(Variable::Null));
1548        }
1549
1550        let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1551
1552        if values.is_empty() {
1553            return Ok(Rc::new(Variable::Null));
1554        }
1555
1556        values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1557
1558        let n = values.len();
1559        let pos = q * (n - 1) as f64;
1560        let lower = pos.floor() as usize;
1561        let upper = pos.ceil() as usize;
1562        let frac = pos - lower as f64;
1563
1564        let result = if lower == upper {
1565            values[lower]
1566        } else {
1567            values[lower] * (1.0 - frac) + values[upper] * frac
1568        };
1569
1570        Ok(Rc::new(Variable::Number(
1571            serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
1572        )))
1573    }
1574}
1575
1576// =============================================================================
1577// moving_avg(array, window) -> array
1578// Simple moving average
1579// =============================================================================
1580
1581define_function!(
1582    MovingAvgFn,
1583    vec![ArgumentType::Array, ArgumentType::Number],
1584    None
1585);
1586
1587impl Function for MovingAvgFn {
1588    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1589        self.signature.validate(args, ctx)?;
1590
1591        let arr = args[0].as_array().ok_or_else(|| {
1592            JmespathError::new(
1593                ctx.expression,
1594                0,
1595                ErrorReason::Parse("Expected array argument".to_owned()),
1596            )
1597        })?;
1598
1599        let window = args[1].as_number().ok_or_else(|| {
1600            JmespathError::new(
1601                ctx.expression,
1602                0,
1603                ErrorReason::Parse("Expected number for window size".to_owned()),
1604            )
1605        })? as usize;
1606
1607        if window == 0 {
1608            return Ok(Rc::new(Variable::Null));
1609        }
1610
1611        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1612
1613        if values.is_empty() || window > values.len() {
1614            return Ok(Rc::new(Variable::Array(vec![])));
1615        }
1616
1617        let mut result: Vec<Rcvar> = Vec::new();
1618
1619        // Compute moving averages for each position where we have enough data
1620        for i in 0..values.len() {
1621            if i + 1 < window {
1622                // Not enough data yet, use null
1623                result.push(Rc::new(Variable::Null));
1624            } else {
1625                let start = i + 1 - window;
1626                let sum: f64 = values[start..=i].iter().sum();
1627                let avg = sum / window as f64;
1628                result.push(Rc::new(Variable::Number(
1629                    serde_json::Number::from_f64(avg)
1630                        .unwrap_or_else(|| serde_json::Number::from(0)),
1631                )));
1632            }
1633        }
1634
1635        Ok(Rc::new(Variable::Array(result)))
1636    }
1637}
1638
1639// =============================================================================
1640// ewma(array, alpha) -> array
1641// Exponential weighted moving average
1642// =============================================================================
1643
1644define_function!(
1645    EwmaFn,
1646    vec![ArgumentType::Array, ArgumentType::Number],
1647    None
1648);
1649
1650impl Function for EwmaFn {
1651    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1652        self.signature.validate(args, ctx)?;
1653
1654        let arr = args[0].as_array().ok_or_else(|| {
1655            JmespathError::new(
1656                ctx.expression,
1657                0,
1658                ErrorReason::Parse("Expected array argument".to_owned()),
1659            )
1660        })?;
1661
1662        let alpha = args[1].as_number().ok_or_else(|| {
1663            JmespathError::new(
1664                ctx.expression,
1665                0,
1666                ErrorReason::Parse("Expected number for alpha".to_owned()),
1667            )
1668        })?;
1669
1670        if !(0.0..=1.0).contains(&alpha) {
1671            return Ok(Rc::new(Variable::Null));
1672        }
1673
1674        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1675
1676        if values.is_empty() {
1677            return Ok(Rc::new(Variable::Array(vec![])));
1678        }
1679
1680        let mut result: Vec<Rcvar> = Vec::new();
1681        let mut ewma = values[0];
1682
1683        for value in &values {
1684            ewma = alpha * value + (1.0 - alpha) * ewma;
1685            result.push(Rc::new(Variable::Number(
1686                serde_json::Number::from_f64(ewma).unwrap_or_else(|| serde_json::Number::from(0)),
1687            )));
1688        }
1689
1690        Ok(Rc::new(Variable::Array(result)))
1691    }
1692}
1693
1694// =============================================================================
1695// covariance(arr1, arr2) -> number
1696// Covariance between two arrays
1697// =============================================================================
1698
1699define_function!(
1700    CovarianceFn,
1701    vec![ArgumentType::Array, ArgumentType::Array],
1702    None
1703);
1704
1705impl Function for CovarianceFn {
1706    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1707        self.signature.validate(args, ctx)?;
1708
1709        let arr1 = args[0].as_array().ok_or_else(|| {
1710            JmespathError::new(
1711                ctx.expression,
1712                0,
1713                ErrorReason::Parse("Expected array argument".to_owned()),
1714            )
1715        })?;
1716
1717        let arr2 = args[1].as_array().ok_or_else(|| {
1718            JmespathError::new(
1719                ctx.expression,
1720                0,
1721                ErrorReason::Parse("Expected array argument".to_owned()),
1722            )
1723        })?;
1724
1725        let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_number()).collect();
1726        let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_number()).collect();
1727
1728        if values1.is_empty() || values1.len() != values2.len() {
1729            return Ok(Rc::new(Variable::Null));
1730        }
1731
1732        let n = values1.len() as f64;
1733        let mean1: f64 = values1.iter().sum::<f64>() / n;
1734        let mean2: f64 = values2.iter().sum::<f64>() / n;
1735
1736        let cov: f64 = values1
1737            .iter()
1738            .zip(values2.iter())
1739            .map(|(x, y)| (x - mean1) * (y - mean2))
1740            .sum::<f64>()
1741            / n;
1742
1743        Ok(Rc::new(Variable::Number(
1744            serde_json::Number::from_f64(cov).unwrap_or_else(|| serde_json::Number::from(0)),
1745        )))
1746    }
1747}
1748
1749// =============================================================================
1750// standardize(array) -> array
1751// Standardize to mean=0, std=1 (z-score normalization)
1752// =============================================================================
1753
1754define_function!(StandardizeFn, vec![ArgumentType::Array], None);
1755
1756impl Function for StandardizeFn {
1757    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1758        self.signature.validate(args, ctx)?;
1759
1760        let arr = args[0].as_array().ok_or_else(|| {
1761            JmespathError::new(
1762                ctx.expression,
1763                0,
1764                ErrorReason::Parse("Expected array argument".to_owned()),
1765            )
1766        })?;
1767
1768        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1769
1770        if values.is_empty() {
1771            return Ok(Rc::new(Variable::Array(vec![])));
1772        }
1773
1774        let n = values.len() as f64;
1775        let mean: f64 = values.iter().sum::<f64>() / n;
1776        let variance: f64 = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n;
1777        let std_dev = variance.sqrt();
1778
1779        let result: Vec<Rcvar> = values
1780            .iter()
1781            .map(|x| {
1782                let standardized = if std_dev.abs() < f64::EPSILON {
1783                    0.0
1784                } else {
1785                    (x - mean) / std_dev
1786                };
1787                Rc::new(Variable::Number(
1788                    serde_json::Number::from_f64(standardized)
1789                        .unwrap_or_else(|| serde_json::Number::from(0)),
1790                ))
1791            })
1792            .collect();
1793
1794        Ok(Rc::new(Variable::Array(result)))
1795    }
1796}
1797
1798// =============================================================================
1799// quartiles(array) -> object {q1, q2, q3, min, max, iqr}
1800// =============================================================================
1801
1802define_function!(QuartilesFn, vec![ArgumentType::Array], None);
1803
1804impl Function for QuartilesFn {
1805    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1806        self.signature.validate(args, ctx)?;
1807
1808        let arr = args[0].as_array().ok_or_else(|| {
1809            JmespathError::new(
1810                ctx.expression,
1811                0,
1812                ErrorReason::Parse("Expected array argument".to_owned()),
1813            )
1814        })?;
1815
1816        let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1817
1818        if values.is_empty() {
1819            return Ok(Rc::new(Variable::Null));
1820        }
1821
1822        values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1823
1824        let n = values.len();
1825        let min = values[0];
1826        let max = values[n - 1];
1827
1828        let q1 = percentile_value(&values, 25.0);
1829        let q2 = percentile_value(&values, 50.0);
1830        let q3 = percentile_value(&values, 75.0);
1831        let iqr = q3 - q1;
1832
1833        let mut result = std::collections::BTreeMap::new();
1834        result.insert(
1835            "min".to_string(),
1836            Rc::new(Variable::Number(
1837                serde_json::Number::from_f64(min).unwrap_or_else(|| serde_json::Number::from(0)),
1838            )) as Rcvar,
1839        );
1840        result.insert(
1841            "q1".to_string(),
1842            Rc::new(Variable::Number(
1843                serde_json::Number::from_f64(q1).unwrap_or_else(|| serde_json::Number::from(0)),
1844            )) as Rcvar,
1845        );
1846        result.insert(
1847            "q2".to_string(),
1848            Rc::new(Variable::Number(
1849                serde_json::Number::from_f64(q2).unwrap_or_else(|| serde_json::Number::from(0)),
1850            )) as Rcvar,
1851        );
1852        result.insert(
1853            "q3".to_string(),
1854            Rc::new(Variable::Number(
1855                serde_json::Number::from_f64(q3).unwrap_or_else(|| serde_json::Number::from(0)),
1856            )) as Rcvar,
1857        );
1858        result.insert(
1859            "max".to_string(),
1860            Rc::new(Variable::Number(
1861                serde_json::Number::from_f64(max).unwrap_or_else(|| serde_json::Number::from(0)),
1862            )) as Rcvar,
1863        );
1864        result.insert(
1865            "iqr".to_string(),
1866            Rc::new(Variable::Number(
1867                serde_json::Number::from_f64(iqr).unwrap_or_else(|| serde_json::Number::from(0)),
1868            )) as Rcvar,
1869        );
1870
1871        Ok(Rc::new(Variable::Object(result)))
1872    }
1873}
1874
1875fn percentile_value(sorted_values: &[f64], p: f64) -> f64 {
1876    let n = sorted_values.len();
1877    if n == 1 {
1878        return sorted_values[0];
1879    }
1880
1881    let k = (p / 100.0) * (n - 1) as f64;
1882    let f = k.floor() as usize;
1883    let c = k.ceil() as usize;
1884
1885    if f == c {
1886        sorted_values[f]
1887    } else {
1888        let d = k - f as f64;
1889        sorted_values[f] * (1.0 - d) + sorted_values[c] * d
1890    }
1891}
1892
1893// =============================================================================
1894// outliers_iqr(array, multiplier?) -> array (elements outside multiplier*IQR)
1895// =============================================================================
1896
1897define_function!(
1898    OutliersIqrFn,
1899    vec![ArgumentType::Array],
1900    Some(ArgumentType::Number)
1901);
1902
1903impl Function for OutliersIqrFn {
1904    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1905        self.signature.validate(args, ctx)?;
1906
1907        let arr = args[0].as_array().ok_or_else(|| {
1908            JmespathError::new(
1909                ctx.expression,
1910                0,
1911                ErrorReason::Parse("Expected array argument".to_owned()),
1912            )
1913        })?;
1914
1915        let multiplier = if args.len() > 1 {
1916            args[1].as_number().unwrap_or(1.5)
1917        } else {
1918            1.5
1919        };
1920
1921        let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1922
1923        if values.is_empty() {
1924            return Ok(Rc::new(Variable::Array(vec![])));
1925        }
1926
1927        values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1928
1929        let q1 = percentile_value(&values, 25.0);
1930        let q3 = percentile_value(&values, 75.0);
1931        let iqr = q3 - q1;
1932
1933        let lower_bound = q1 - multiplier * iqr;
1934        let upper_bound = q3 + multiplier * iqr;
1935
1936        let outliers: Vec<Rcvar> = arr
1937            .iter()
1938            .filter(|v| {
1939                if let Some(n) = v.as_number() {
1940                    n < lower_bound || n > upper_bound
1941                } else {
1942                    false
1943                }
1944            })
1945            .cloned()
1946            .collect();
1947
1948        Ok(Rc::new(Variable::Array(outliers)))
1949    }
1950}
1951
1952// =============================================================================
1953// outliers_zscore(array, threshold?) -> array (elements with |z-score| > threshold)
1954// =============================================================================
1955
1956define_function!(
1957    OutliersZscoreFn,
1958    vec![ArgumentType::Array],
1959    Some(ArgumentType::Number)
1960);
1961
1962impl Function for OutliersZscoreFn {
1963    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
1964        self.signature.validate(args, ctx)?;
1965
1966        let arr = args[0].as_array().ok_or_else(|| {
1967            JmespathError::new(
1968                ctx.expression,
1969                0,
1970                ErrorReason::Parse("Expected array argument".to_owned()),
1971            )
1972        })?;
1973
1974        let threshold = if args.len() > 1 {
1975            args[1].as_number().unwrap_or(2.0)
1976        } else {
1977            2.0
1978        };
1979
1980        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
1981
1982        if values.is_empty() {
1983            return Ok(Rc::new(Variable::Array(vec![])));
1984        }
1985
1986        let n = values.len() as f64;
1987        let mean: f64 = values.iter().sum::<f64>() / n;
1988        let variance: f64 = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
1989        let stddev = variance.sqrt();
1990
1991        if stddev.abs() < f64::EPSILON {
1992            return Ok(Rc::new(Variable::Array(vec![])));
1993        }
1994
1995        let outliers: Vec<Rcvar> = arr
1996            .iter()
1997            .filter(|v| {
1998                if let Some(n) = v.as_number() {
1999                    let z = (n - mean) / stddev;
2000                    z.abs() > threshold
2001                } else {
2002                    false
2003                }
2004            })
2005            .cloned()
2006            .collect();
2007
2008        Ok(Rc::new(Variable::Array(outliers)))
2009    }
2010}
2011
2012// =============================================================================
2013// trend(array) -> string (detect trend: "increasing", "decreasing", "stable")
2014// =============================================================================
2015
2016define_function!(TrendFn, vec![ArgumentType::Array], None);
2017
2018impl Function for TrendFn {
2019    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
2020        self.signature.validate(args, ctx)?;
2021
2022        let arr = args[0].as_array().ok_or_else(|| {
2023            JmespathError::new(
2024                ctx.expression,
2025                0,
2026                ErrorReason::Parse("Expected array of numbers".to_owned()),
2027            )
2028        })?;
2029
2030        if arr.len() < 2 {
2031            return Ok(Rc::new(Variable::String("stable".to_string())));
2032        }
2033
2034        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
2035
2036        if values.len() < 2 {
2037            return Ok(Rc::new(Variable::String("stable".to_string())));
2038        }
2039
2040        // Calculate linear regression slope
2041        let n = values.len() as f64;
2042        let sum_x: f64 = (0..values.len()).map(|i| i as f64).sum();
2043        let sum_y: f64 = values.iter().sum();
2044        let sum_xy: f64 = values.iter().enumerate().map(|(i, y)| i as f64 * y).sum();
2045        let sum_x2: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
2046
2047        let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x.powi(2));
2048
2049        // Determine trend based on slope relative to data magnitude
2050        let mean: f64 = sum_y / n;
2051        let threshold = mean.abs() * 0.01; // 1% of mean as threshold
2052
2053        let trend = if slope > threshold {
2054            "increasing"
2055        } else if slope < -threshold {
2056            "decreasing"
2057        } else {
2058            "stable"
2059        };
2060
2061        Ok(Rc::new(Variable::String(trend.to_string())))
2062    }
2063}
2064
2065// =============================================================================
2066// trend_slope(array) -> number (linear regression slope)
2067// =============================================================================
2068
2069define_function!(TrendSlopeFn, vec![ArgumentType::Array], None);
2070
2071impl Function for TrendSlopeFn {
2072    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
2073        self.signature.validate(args, ctx)?;
2074
2075        let arr = args[0].as_array().ok_or_else(|| {
2076            JmespathError::new(
2077                ctx.expression,
2078                0,
2079                ErrorReason::Parse("Expected array of numbers".to_owned()),
2080            )
2081        })?;
2082
2083        if arr.len() < 2 {
2084            return Ok(Rc::new(Variable::Number(
2085                serde_json::Number::from_f64(0.0).unwrap(),
2086            )));
2087        }
2088
2089        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
2090
2091        if values.len() < 2 {
2092            return Ok(Rc::new(Variable::Number(
2093                serde_json::Number::from_f64(0.0).unwrap(),
2094            )));
2095        }
2096
2097        // Calculate linear regression slope
2098        let n = values.len() as f64;
2099        let sum_x: f64 = (0..values.len()).map(|i| i as f64).sum();
2100        let sum_y: f64 = values.iter().sum();
2101        let sum_xy: f64 = values.iter().enumerate().map(|(i, y)| i as f64 * y).sum();
2102        let sum_x2: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
2103
2104        let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x.powi(2));
2105
2106        Ok(Rc::new(Variable::Number(
2107            serde_json::Number::from_f64(slope).unwrap_or_else(|| serde_json::Number::from(0)),
2108        )))
2109    }
2110}
2111
2112// =============================================================================
2113// rate_of_change(array) -> array (percentage change between consecutive elements)
2114// =============================================================================
2115
2116define_function!(RateOfChangeFn, vec![ArgumentType::Array], None);
2117
2118impl Function for RateOfChangeFn {
2119    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
2120        self.signature.validate(args, ctx)?;
2121
2122        let arr = args[0].as_array().ok_or_else(|| {
2123            JmespathError::new(
2124                ctx.expression,
2125                0,
2126                ErrorReason::Parse("Expected array of numbers".to_owned()),
2127            )
2128        })?;
2129
2130        if arr.len() < 2 {
2131            return Ok(Rc::new(Variable::Array(vec![])));
2132        }
2133
2134        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
2135
2136        if values.len() < 2 {
2137            return Ok(Rc::new(Variable::Array(vec![])));
2138        }
2139
2140        let changes: Vec<Rcvar> = values
2141            .windows(2)
2142            .map(|w| {
2143                let prev = w[0];
2144                let curr = w[1];
2145                let pct_change = if prev != 0.0 {
2146                    ((curr - prev) / prev) * 100.0
2147                } else {
2148                    0.0
2149                };
2150                Rc::new(Variable::Number(
2151                    serde_json::Number::from_f64(pct_change)
2152                        .unwrap_or_else(|| serde_json::Number::from(0)),
2153                )) as Rcvar
2154            })
2155            .collect();
2156
2157        Ok(Rc::new(Variable::Array(changes)))
2158    }
2159}
2160
2161// =============================================================================
2162// cumulative_sum(array) -> array (running total)
2163// =============================================================================
2164
2165define_function!(CumulativeSumFn, vec![ArgumentType::Array], None);
2166
2167impl Function for CumulativeSumFn {
2168    fn evaluate(&self, args: &[Rcvar], ctx: &mut Context<'_>) -> Result<Rcvar, JmespathError> {
2169        self.signature.validate(args, ctx)?;
2170
2171        let arr = args[0].as_array().ok_or_else(|| {
2172            JmespathError::new(
2173                ctx.expression,
2174                0,
2175                ErrorReason::Parse("Expected array of numbers".to_owned()),
2176            )
2177        })?;
2178
2179        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_number()).collect();
2180
2181        let mut running_sum = 0.0;
2182        let cumsum: Vec<Rcvar> = values
2183            .iter()
2184            .map(|v| {
2185                running_sum += v;
2186                Rc::new(Variable::Number(
2187                    serde_json::Number::from_f64(running_sum)
2188                        .unwrap_or_else(|| serde_json::Number::from(0)),
2189                )) as Rcvar
2190            })
2191            .collect();
2192
2193        Ok(Rc::new(Variable::Array(cumsum)))
2194    }
2195}
2196
2197#[cfg(test)]
2198mod tests {
2199    use super::*;
2200    use jmespath::Runtime;
2201
2202    fn setup_runtime() -> Runtime {
2203        let mut runtime = Runtime::new();
2204        runtime.register_builtin_functions();
2205        register(&mut runtime);
2206        runtime
2207    }
2208
2209    #[test]
2210    #[allow(clippy::approx_constant)]
2211    fn test_round() {
2212        let runtime = setup_runtime();
2213        let expr = runtime.compile("round(`3.14159`, `2`)").unwrap();
2214        let result = expr.search(&Variable::Null).unwrap();
2215        assert!((result.as_number().unwrap() - 3.14_f64).abs() < 0.001);
2216    }
2217
2218    #[test]
2219    fn test_sqrt() {
2220        let runtime = setup_runtime();
2221        let expr = runtime.compile("sqrt(`16`)").unwrap();
2222        let result = expr.search(&Variable::Null).unwrap();
2223        assert_eq!(result.as_number().unwrap() as i64, 4);
2224    }
2225
2226    #[test]
2227    fn test_clamp() {
2228        let runtime = setup_runtime();
2229        let expr = runtime.compile("clamp(`5`, `0`, `3`)").unwrap();
2230        let result = expr.search(&Variable::Null).unwrap();
2231        assert_eq!(result.as_number().unwrap() as i64, 3);
2232    }
2233
2234    #[test]
2235    fn test_add() {
2236        let runtime = setup_runtime();
2237        let expr = runtime.compile("add(`1`, `2`)").unwrap();
2238        let result = expr.search(&Variable::Null).unwrap();
2239        assert_eq!(result.as_number().unwrap() as i64, 3);
2240    }
2241
2242    #[test]
2243    fn test_subtract() {
2244        let runtime = setup_runtime();
2245        let expr = runtime.compile("subtract(`10`, `3`)").unwrap();
2246        let result = expr.search(&Variable::Null).unwrap();
2247        assert_eq!(result.as_number().unwrap() as i64, 7);
2248    }
2249
2250    #[test]
2251    fn test_multiply() {
2252        let runtime = setup_runtime();
2253        let expr = runtime.compile("multiply(`4`, `5`)").unwrap();
2254        let result = expr.search(&Variable::Null).unwrap();
2255        assert_eq!(result.as_number().unwrap() as i64, 20);
2256    }
2257
2258    #[test]
2259    fn test_divide() {
2260        let runtime = setup_runtime();
2261        let expr = runtime.compile("divide(`10`, `4`)").unwrap();
2262        let result = expr.search(&Variable::Null).unwrap();
2263        assert_eq!(result.as_number().unwrap(), 2.5);
2264    }
2265
2266    #[test]
2267    fn test_mode_numbers() {
2268        let runtime = setup_runtime();
2269        let expr = runtime.compile("mode(`[1, 2, 2, 3]`)").unwrap();
2270        let result = expr.search(&Variable::Null).unwrap();
2271        assert_eq!(result.as_number().unwrap() as i64, 2);
2272    }
2273
2274    #[test]
2275    fn test_mode_strings() {
2276        let runtime = setup_runtime();
2277        let expr = runtime
2278            .compile("mode(`[\"a\", \"b\", \"a\", \"c\"]`)")
2279            .unwrap();
2280        let result = expr.search(&Variable::Null).unwrap();
2281        assert_eq!(result.as_string().unwrap(), "a");
2282    }
2283
2284    #[test]
2285    fn test_mode_empty() {
2286        let runtime = setup_runtime();
2287        let expr = runtime.compile("mode(`[]`)").unwrap();
2288        let result = expr.search(&Variable::Null).unwrap();
2289        assert!(result.is_null());
2290    }
2291
2292    #[test]
2293    fn test_to_fixed() {
2294        let runtime = setup_runtime();
2295        let expr = runtime.compile("to_fixed(`3.14159`, `2`)").unwrap();
2296        let result = expr.search(&Variable::Null).unwrap();
2297        assert_eq!(result.as_string().unwrap(), "3.14");
2298    }
2299
2300    #[test]
2301    fn test_to_fixed_padding() {
2302        let runtime = setup_runtime();
2303        let expr = runtime.compile("to_fixed(`3.1`, `3`)").unwrap();
2304        let result = expr.search(&Variable::Null).unwrap();
2305        assert_eq!(result.as_string().unwrap(), "3.100");
2306    }
2307
2308    #[test]
2309    fn test_format_number_with_separators() {
2310        let runtime = setup_runtime();
2311        let expr = runtime.compile("format_number(`1234567.89`, `2`)").unwrap();
2312        let result = expr.search(&Variable::Null).unwrap();
2313        assert_eq!(result.as_string().unwrap(), "1,234,567.89");
2314    }
2315
2316    #[test]
2317    fn test_format_number_with_k_suffix() {
2318        let runtime = setup_runtime();
2319        let expr = runtime.compile("format_number(`1500`, `1`, 'k')").unwrap();
2320        let result = expr.search(&Variable::Null).unwrap();
2321        assert_eq!(result.as_string().unwrap(), "1.5k");
2322    }
2323
2324    #[test]
2325    fn test_format_number_with_m_suffix() {
2326        let runtime = setup_runtime();
2327        let expr = runtime
2328            .compile("format_number(`1500000`, `1`, 'M')")
2329            .unwrap();
2330        let result = expr.search(&Variable::Null).unwrap();
2331        assert_eq!(result.as_string().unwrap(), "1.5M");
2332    }
2333
2334    #[test]
2335    fn test_format_number_auto_suffix() {
2336        let runtime = setup_runtime();
2337        let expr = runtime
2338            .compile("format_number(`1500000000`, `2`, 'auto')")
2339            .unwrap();
2340        let result = expr.search(&Variable::Null).unwrap();
2341        assert_eq!(result.as_string().unwrap(), "1.50B");
2342    }
2343
2344    #[test]
2345    fn test_histogram() {
2346        let runtime = setup_runtime();
2347        let expr = runtime.compile("histogram(@, `3`)").unwrap();
2348        let data: Variable = serde_json::from_str("[1, 2, 3, 4, 5, 6, 7, 8, 9]").unwrap();
2349        let result = expr.search(&data).unwrap();
2350        let arr = result.as_array().unwrap();
2351        assert_eq!(arr.len(), 3);
2352        // Each bin should have 3 values
2353        for bin in arr {
2354            let obj = bin.as_object().unwrap();
2355            assert!(obj.contains_key("min"));
2356            assert!(obj.contains_key("max"));
2357            assert!(obj.contains_key("count"));
2358        }
2359    }
2360
2361    #[test]
2362    fn test_normalize() {
2363        let runtime = setup_runtime();
2364        let expr = runtime.compile("normalize(@)").unwrap();
2365        let data: Variable = serde_json::from_str("[0, 50, 100]").unwrap();
2366        let result = expr.search(&data).unwrap();
2367        let arr = result.as_array().unwrap();
2368        assert_eq!(arr.len(), 3);
2369        assert!((arr[0].as_number().unwrap() - 0.0).abs() < 0.001);
2370        assert!((arr[1].as_number().unwrap() - 0.5).abs() < 0.001);
2371        assert!((arr[2].as_number().unwrap() - 1.0).abs() < 0.001);
2372    }
2373
2374    #[test]
2375    fn test_z_score() {
2376        let runtime = setup_runtime();
2377        let expr = runtime.compile("z_score(@)").unwrap();
2378        let data: Variable = serde_json::from_str("[1, 2, 3, 4, 5]").unwrap();
2379        let result = expr.search(&data).unwrap();
2380        let arr = result.as_array().unwrap();
2381        assert_eq!(arr.len(), 5);
2382        // Middle value (3) should have z-score of 0
2383        assert!((arr[2].as_number().unwrap() - 0.0).abs() < 0.001);
2384    }
2385
2386    #[test]
2387    fn test_correlation_positive() {
2388        let runtime = setup_runtime();
2389        let expr = runtime
2390            .compile("correlation(`[1, 2, 3]`, `[1, 2, 3]`)")
2391            .unwrap();
2392        let result = expr.search(&Variable::Null).unwrap();
2393        assert!((result.as_number().unwrap() - 1.0).abs() < 0.001);
2394    }
2395
2396    #[test]
2397    fn test_correlation_negative() {
2398        let runtime = setup_runtime();
2399        let expr = runtime
2400            .compile("correlation(`[1, 2, 3]`, `[3, 2, 1]`)")
2401            .unwrap();
2402        let result = expr.search(&Variable::Null).unwrap();
2403        assert!((result.as_number().unwrap() - (-1.0)).abs() < 0.001);
2404    }
2405
2406    #[test]
2407    fn test_quantile_median() {
2408        let runtime = setup_runtime();
2409        let expr = runtime
2410            .compile("quantile(`[1, 2, 3, 4, 5]`, `0.5`)")
2411            .unwrap();
2412        let result = expr.search(&Variable::Null).unwrap();
2413        assert_eq!(result.as_number().unwrap(), 3.0);
2414    }
2415
2416    #[test]
2417    fn test_quantile_quartiles() {
2418        let runtime = setup_runtime();
2419        // First quartile
2420        let expr = runtime
2421            .compile("quantile(`[1, 2, 3, 4, 5]`, `0.25`)")
2422            .unwrap();
2423        let result = expr.search(&Variable::Null).unwrap();
2424        assert_eq!(result.as_number().unwrap(), 2.0);
2425
2426        // Third quartile
2427        let expr = runtime
2428            .compile("quantile(`[1, 2, 3, 4, 5]`, `0.75`)")
2429            .unwrap();
2430        let result = expr.search(&Variable::Null).unwrap();
2431        assert_eq!(result.as_number().unwrap(), 4.0);
2432    }
2433
2434    #[test]
2435    fn test_moving_avg() {
2436        let runtime = setup_runtime();
2437        let expr = runtime
2438            .compile("moving_avg(`[1, 2, 3, 4, 5, 6]`, `3`)")
2439            .unwrap();
2440        let result = expr.search(&Variable::Null).unwrap();
2441        let arr = result.as_array().unwrap();
2442        assert_eq!(arr.len(), 6);
2443        assert!(arr[0].is_null());
2444        assert!(arr[1].is_null());
2445        assert_eq!(arr[2].as_number().unwrap(), 2.0); // (1+2+3)/3
2446        assert_eq!(arr[3].as_number().unwrap(), 3.0); // (2+3+4)/3
2447        assert_eq!(arr[4].as_number().unwrap(), 4.0); // (3+4+5)/3
2448        assert_eq!(arr[5].as_number().unwrap(), 5.0); // (4+5+6)/3
2449    }
2450
2451    #[test]
2452    fn test_ewma() {
2453        let runtime = setup_runtime();
2454        let expr = runtime.compile("ewma(`[1, 2, 3, 4, 5]`, `0.5`)").unwrap();
2455        let result = expr.search(&Variable::Null).unwrap();
2456        let arr = result.as_array().unwrap();
2457        assert_eq!(arr.len(), 5);
2458        // First value is just the first value
2459        assert_eq!(arr[0].as_number().unwrap(), 1.0);
2460        // Subsequent values: alpha * current + (1-alpha) * prev_ewma
2461        assert_eq!(arr[1].as_number().unwrap(), 1.5); // 0.5*2 + 0.5*1
2462        assert_eq!(arr[2].as_number().unwrap(), 2.25); // 0.5*3 + 0.5*1.5
2463    }
2464
2465    #[test]
2466    fn test_covariance() {
2467        let runtime = setup_runtime();
2468        let expr = runtime
2469            .compile("covariance(`[1, 2, 3]`, `[1, 2, 3]`)")
2470            .unwrap();
2471        let result = expr.search(&Variable::Null).unwrap();
2472        // Variance of [1,2,3] is 2/3
2473        assert!((result.as_number().unwrap() - 0.666666).abs() < 0.01);
2474    }
2475
2476    #[test]
2477    fn test_covariance_negative() {
2478        let runtime = setup_runtime();
2479        let expr = runtime
2480            .compile("covariance(`[1, 2, 3]`, `[3, 2, 1]`)")
2481            .unwrap();
2482        let result = expr.search(&Variable::Null).unwrap();
2483        assert!((result.as_number().unwrap() - (-0.666666)).abs() < 0.01);
2484    }
2485
2486    #[test]
2487    fn test_standardize() {
2488        let runtime = setup_runtime();
2489        let expr = runtime
2490            .compile("standardize(`[10, 20, 30, 40, 50]`)")
2491            .unwrap();
2492        let result = expr.search(&Variable::Null).unwrap();
2493        let arr = result.as_array().unwrap();
2494        assert_eq!(arr.len(), 5);
2495        // Mean is 30, std is ~14.14
2496        // First value: (10-30)/14.14 ≈ -1.41
2497        assert!((arr[0].as_number().unwrap() - (-1.414)).abs() < 0.01);
2498        // Middle value should be 0
2499        assert!(arr[2].as_number().unwrap().abs() < 0.001);
2500        // Last value: (50-30)/14.14 ≈ 1.41
2501        assert!((arr[4].as_number().unwrap() - 1.414).abs() < 0.01);
2502    }
2503
2504    #[test]
2505    fn test_trend_increasing() {
2506        let runtime = setup_runtime();
2507        let expr = runtime.compile("trend(`[1, 2, 3, 5, 8]`)").unwrap();
2508        let result = expr.search(&Variable::Null).unwrap();
2509        assert_eq!(result.as_string().unwrap(), "increasing");
2510    }
2511
2512    #[test]
2513    fn test_trend_decreasing() {
2514        let runtime = setup_runtime();
2515        let expr = runtime.compile("trend(`[10, 9, 8, 7, 6]`)").unwrap();
2516        let result = expr.search(&Variable::Null).unwrap();
2517        assert_eq!(result.as_string().unwrap(), "decreasing");
2518    }
2519
2520    #[test]
2521    fn test_trend_stable() {
2522        let runtime = setup_runtime();
2523        let expr = runtime.compile("trend(`[5, 5, 5, 5, 5]`)").unwrap();
2524        let result = expr.search(&Variable::Null).unwrap();
2525        assert_eq!(result.as_string().unwrap(), "stable");
2526    }
2527
2528    #[test]
2529    fn test_trend_slope() {
2530        let runtime = setup_runtime();
2531        let expr = runtime.compile("trend_slope(`[0, 1, 2, 3, 4]`)").unwrap();
2532        let result = expr.search(&Variable::Null).unwrap();
2533        // Perfect linear increase with slope 1
2534        assert!((result.as_number().unwrap() - 1.0).abs() < 0.001);
2535    }
2536
2537    #[test]
2538    fn test_rate_of_change() {
2539        let runtime = setup_runtime();
2540        let expr = runtime
2541            .compile("rate_of_change(`[100, 110, 105]`)")
2542            .unwrap();
2543        let result = expr.search(&Variable::Null).unwrap();
2544        let arr = result.as_array().unwrap();
2545        assert_eq!(arr.len(), 2);
2546        // 100 -> 110 = 10% increase
2547        assert!((arr[0].as_number().unwrap() - 10.0).abs() < 0.01);
2548        // 110 -> 105 = -4.545% decrease
2549        assert!((arr[1].as_number().unwrap() - (-4.545)).abs() < 0.01);
2550    }
2551
2552    #[test]
2553    fn test_cumulative_sum() {
2554        let runtime = setup_runtime();
2555        let expr = runtime.compile("cumulative_sum(`[1, 2, 3, 4]`)").unwrap();
2556        let result = expr.search(&Variable::Null).unwrap();
2557        let arr = result.as_array().unwrap();
2558        assert_eq!(arr.len(), 4);
2559        assert_eq!(arr[0].as_number().unwrap(), 1.0);
2560        assert_eq!(arr[1].as_number().unwrap(), 3.0);
2561        assert_eq!(arr[2].as_number().unwrap(), 6.0);
2562        assert_eq!(arr[3].as_number().unwrap(), 10.0);
2563    }
2564}