Skip to main content

jpx_core/extensions/
math.rs

1//! Mathematical functions.
2
3use std::collections::HashSet;
4
5use serde_json::{Number, Value};
6
7use crate::functions::{Function, number_value};
8use crate::interpreter::SearchResult;
9use crate::registry::register_if_enabled;
10use crate::{Context, Runtime, arg, defn};
11
12/// Register only the math functions that are in the enabled set.
13pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
14    register_if_enabled(runtime, "round", enabled, Box::new(RoundFn::new()));
15    register_if_enabled(runtime, "floor_fn", enabled, Box::new(FloorFnExt::new()));
16    register_if_enabled(runtime, "ceil_fn", enabled, Box::new(CeilFnExt::new()));
17    register_if_enabled(runtime, "abs_fn", enabled, Box::new(AbsFnExt::new()));
18    register_if_enabled(runtime, "mod_fn", enabled, Box::new(ModFn::new()));
19    register_if_enabled(runtime, "pow", enabled, Box::new(PowFn::new()));
20    register_if_enabled(runtime, "sqrt", enabled, Box::new(SqrtFn::new()));
21    register_if_enabled(runtime, "log", enabled, Box::new(LogFn::new()));
22    register_if_enabled(runtime, "clamp", enabled, Box::new(ClampFn::new()));
23    register_if_enabled(runtime, "median", enabled, Box::new(MedianFn::new()));
24    register_if_enabled(
25        runtime,
26        "percentile",
27        enabled,
28        Box::new(PercentileFn::new()),
29    );
30    register_if_enabled(runtime, "variance", enabled, Box::new(VarianceFn::new()));
31    register_if_enabled(runtime, "stddev", enabled, Box::new(StddevFn::new()));
32    register_if_enabled(runtime, "sin", enabled, Box::new(SinFn::new()));
33    register_if_enabled(runtime, "cos", enabled, Box::new(CosFn::new()));
34    register_if_enabled(runtime, "tan", enabled, Box::new(TanFn::new()));
35    register_if_enabled(runtime, "asin", enabled, Box::new(AsinFn::new()));
36    register_if_enabled(runtime, "acos", enabled, Box::new(AcosFn::new()));
37    register_if_enabled(runtime, "atan", enabled, Box::new(AtanFn::new()));
38    register_if_enabled(runtime, "atan2", enabled, Box::new(Atan2Fn::new()));
39    register_if_enabled(runtime, "deg_to_rad", enabled, Box::new(DegToRadFn::new()));
40    register_if_enabled(runtime, "rad_to_deg", enabled, Box::new(RadToDegFn::new()));
41    register_if_enabled(runtime, "sign", enabled, Box::new(SignFn::new()));
42    register_if_enabled(runtime, "add", enabled, Box::new(AddFn::new()));
43    register_if_enabled(runtime, "subtract", enabled, Box::new(SubtractFn::new()));
44    register_if_enabled(runtime, "multiply", enabled, Box::new(MultiplyFn::new()));
45    register_if_enabled(runtime, "divide", enabled, Box::new(DivideFn::new()));
46    register_if_enabled(runtime, "mode", enabled, Box::new(ModeFn::new()));
47    register_if_enabled(runtime, "to_fixed", enabled, Box::new(ToFixedFn::new()));
48    register_if_enabled(
49        runtime,
50        "format_number",
51        enabled,
52        Box::new(FormatNumberFn::new()),
53    );
54    register_if_enabled(runtime, "histogram", enabled, Box::new(HistogramFn::new()));
55    register_if_enabled(runtime, "normalize", enabled, Box::new(NormalizeFn::new()));
56    register_if_enabled(runtime, "z_score", enabled, Box::new(ZScoreFn::new()));
57    register_if_enabled(
58        runtime,
59        "correlation",
60        enabled,
61        Box::new(CorrelationFn::new()),
62    );
63    register_if_enabled(runtime, "quantile", enabled, Box::new(QuantileFn::new()));
64    register_if_enabled(runtime, "moving_avg", enabled, Box::new(MovingAvgFn::new()));
65    register_if_enabled(runtime, "ewma", enabled, Box::new(EwmaFn::new()));
66    register_if_enabled(
67        runtime,
68        "covariance",
69        enabled,
70        Box::new(CovarianceFn::new()),
71    );
72    register_if_enabled(
73        runtime,
74        "standardize",
75        enabled,
76        Box::new(StandardizeFn::new()),
77    );
78    register_if_enabled(runtime, "quartiles", enabled, Box::new(QuartilesFn::new()));
79    register_if_enabled(
80        runtime,
81        "outliers_iqr",
82        enabled,
83        Box::new(OutliersIqrFn::new()),
84    );
85    register_if_enabled(
86        runtime,
87        "outliers_zscore",
88        enabled,
89        Box::new(OutliersZscoreFn::new()),
90    );
91    register_if_enabled(runtime, "skew", enabled, Box::new(SkewFn::new()));
92    register_if_enabled(runtime, "kurtosis", enabled, Box::new(KurtosisFn::new()));
93    register_if_enabled(runtime, "mad", enabled, Box::new(MadFn::new()));
94    // Time series functions
95    register_if_enabled(runtime, "trend", enabled, Box::new(TrendFn::new()));
96    register_if_enabled(
97        runtime,
98        "trend_slope",
99        enabled,
100        Box::new(TrendSlopeFn::new()),
101    );
102    register_if_enabled(
103        runtime,
104        "rate_of_change",
105        enabled,
106        Box::new(RateOfChangeFn::new()),
107    );
108    register_if_enabled(
109        runtime,
110        "cumulative_sum",
111        enabled,
112        Box::new(CumulativeSumFn::new()),
113    );
114}
115
116// =============================================================================
117// round(number, precision?) -> number
118// =============================================================================
119
120defn!(RoundFn, vec![arg!(number)], Some(arg!(number)));
121
122impl Function for RoundFn {
123    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
124        self.signature.validate(args, ctx)?;
125
126        let n = args[0].as_f64().unwrap();
127
128        let precision = if args.len() > 1 {
129            args[1].as_f64().map(|p| p as i32).unwrap_or(0)
130        } else {
131            0
132        };
133
134        let result = if precision == 0 {
135            n.round()
136        } else {
137            let multiplier = 10_f64.powi(precision);
138            (n * multiplier).round() / multiplier
139        };
140
141        Ok(number_value(result))
142    }
143}
144
145// =============================================================================
146// floor_fn(number) -> number
147// =============================================================================
148
149defn!(FloorFnExt, vec![arg!(number)], None);
150
151impl Function for FloorFnExt {
152    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
153        self.signature.validate(args, ctx)?;
154        let n = args[0].as_f64().unwrap();
155        Ok(Value::Number(Number::from(n.floor() as i64)))
156    }
157}
158
159// =============================================================================
160// ceil_fn(number) -> number
161// =============================================================================
162
163defn!(CeilFnExt, vec![arg!(number)], None);
164
165impl Function for CeilFnExt {
166    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
167        self.signature.validate(args, ctx)?;
168        let n = args[0].as_f64().unwrap();
169        Ok(Value::Number(Number::from(n.ceil() as i64)))
170    }
171}
172
173// =============================================================================
174// abs_fn(number) -> number
175// =============================================================================
176
177defn!(AbsFnExt, vec![arg!(number)], None);
178
179impl Function for AbsFnExt {
180    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
181        self.signature.validate(args, ctx)?;
182        let n = args[0].as_f64().unwrap();
183        Ok(number_value(n.abs()))
184    }
185}
186
187// =============================================================================
188// mod_fn(number, divisor) -> number
189// =============================================================================
190
191defn!(ModFn, vec![arg!(number), arg!(number)], None);
192
193impl Function for ModFn {
194    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
195        self.signature.validate(args, ctx)?;
196
197        let n = args[0].as_f64().unwrap();
198        let divisor = args[1].as_f64().unwrap();
199
200        if divisor == 0.0 {
201            return Err(crate::functions::custom_error(ctx, "Division by zero"));
202        }
203
204        Ok(number_value(n % divisor))
205    }
206}
207
208// =============================================================================
209// pow(base, exponent) -> number
210// =============================================================================
211
212defn!(PowFn, vec![arg!(number), arg!(number)], None);
213
214impl Function for PowFn {
215    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
216        self.signature.validate(args, ctx)?;
217        let base = args[0].as_f64().unwrap();
218        let exp = args[1].as_f64().unwrap();
219        Ok(number_value(base.powf(exp)))
220    }
221}
222
223// =============================================================================
224// sqrt(number) -> number
225// =============================================================================
226
227defn!(SqrtFn, vec![arg!(number)], None);
228
229impl Function for SqrtFn {
230    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
231        self.signature.validate(args, ctx)?;
232        let n = args[0].as_f64().unwrap();
233
234        if n < 0.0 {
235            return Err(crate::functions::custom_error(
236                ctx,
237                "Cannot take square root of negative number",
238            ));
239        }
240
241        Ok(number_value(n.sqrt()))
242    }
243}
244
245// =============================================================================
246// log(number, base?) -> number (default base e)
247// =============================================================================
248
249defn!(LogFn, vec![arg!(number)], Some(arg!(number)));
250
251impl Function for LogFn {
252    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
253        self.signature.validate(args, ctx)?;
254
255        let n = args[0].as_f64().unwrap();
256
257        if n <= 0.0 {
258            return Err(crate::functions::custom_error(
259                ctx,
260                "Logarithm requires positive number",
261            ));
262        }
263
264        let result = if args.len() > 1 {
265            let base = args[1].as_f64().unwrap();
266            n.log(base)
267        } else {
268            n.ln()
269        };
270
271        Ok(number_value(result))
272    }
273}
274
275// =============================================================================
276// clamp(number, min, max) -> number
277// =============================================================================
278
279defn!(
280    ClampFn,
281    vec![arg!(number), arg!(number), arg!(number)],
282    None
283);
284
285impl Function for ClampFn {
286    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
287        self.signature.validate(args, ctx)?;
288
289        let n = args[0].as_f64().unwrap();
290        let min = args[1].as_f64().unwrap();
291        let max = args[2].as_f64().unwrap();
292
293        let result = n.max(min).min(max);
294
295        Ok(number_value(result))
296    }
297}
298
299// =============================================================================
300// median(array) -> number
301// =============================================================================
302
303defn!(MedianFn, vec![arg!(array)], None);
304
305impl Function for MedianFn {
306    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
307        self.signature.validate(args, ctx)?;
308
309        let arr = args[0].as_array().unwrap();
310
311        let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
312
313        if numbers.is_empty() {
314            return Ok(Value::Null);
315        }
316
317        numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
318
319        let len = numbers.len();
320        let median = if len.is_multiple_of(2) {
321            (numbers[len / 2 - 1] + numbers[len / 2]) / 2.0
322        } else {
323            numbers[len / 2]
324        };
325
326        Ok(number_value(median))
327    }
328}
329
330// =============================================================================
331// percentile(array, p) -> number (pth percentile, p in 0-100)
332// =============================================================================
333
334defn!(PercentileFn, vec![arg!(array), arg!(number)], None);
335
336impl Function for PercentileFn {
337    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
338        self.signature.validate(args, ctx)?;
339
340        let arr = args[0].as_array().unwrap();
341        let p = args[1].as_f64().unwrap();
342
343        if !(0.0..=100.0).contains(&p) {
344            return Err(crate::functions::custom_error(
345                ctx,
346                "Percentile must be between 0 and 100",
347            ));
348        }
349
350        let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
351
352        if numbers.is_empty() {
353            return Ok(Value::Null);
354        }
355
356        numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
357
358        let len = numbers.len();
359        if len == 1 {
360            return Ok(number_value(numbers[0]));
361        }
362
363        let rank = (p / 100.0) * (len - 1) as f64;
364        let lower_idx = rank.floor() as usize;
365        let upper_idx = rank.ceil() as usize;
366        let fraction = rank - lower_idx as f64;
367
368        let result = if lower_idx == upper_idx {
369            numbers[lower_idx]
370        } else {
371            numbers[lower_idx] * (1.0 - fraction) + numbers[upper_idx] * fraction
372        };
373
374        Ok(number_value(result))
375    }
376}
377
378// =============================================================================
379// variance(array) -> number (population variance)
380// =============================================================================
381
382defn!(VarianceFn, vec![arg!(array)], None);
383
384impl Function for VarianceFn {
385    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
386        self.signature.validate(args, ctx)?;
387
388        let arr = args[0].as_array().unwrap();
389
390        let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
391
392        if numbers.is_empty() {
393            return Ok(Value::Null);
394        }
395
396        let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
397        let variance =
398            numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
399
400        Ok(number_value(variance))
401    }
402}
403
404// =============================================================================
405// stddev(array) -> number (population standard deviation)
406// =============================================================================
407
408defn!(StddevFn, vec![arg!(array)], None);
409
410impl Function for StddevFn {
411    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
412        self.signature.validate(args, ctx)?;
413
414        let arr = args[0].as_array().unwrap();
415
416        let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
417
418        if numbers.is_empty() {
419            return Ok(Value::Null);
420        }
421
422        let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
423        let variance =
424            numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
425        let stddev = variance.sqrt();
426
427        Ok(number_value(stddev))
428    }
429}
430
431// =============================================================================
432// Trigonometric functions
433// =============================================================================
434
435defn!(SinFn, vec![arg!(number)], None);
436
437impl Function for SinFn {
438    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
439        self.signature.validate(args, ctx)?;
440        let n = args[0].as_f64().unwrap();
441        Ok(number_value(n.sin()))
442    }
443}
444
445defn!(CosFn, vec![arg!(number)], None);
446
447impl Function for CosFn {
448    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
449        self.signature.validate(args, ctx)?;
450        let n = args[0].as_f64().unwrap();
451        Ok(number_value(n.cos()))
452    }
453}
454
455defn!(TanFn, vec![arg!(number)], None);
456
457impl Function for TanFn {
458    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
459        self.signature.validate(args, ctx)?;
460        let n = args[0].as_f64().unwrap();
461        Ok(number_value(n.tan()))
462    }
463}
464
465defn!(AsinFn, vec![arg!(number)], None);
466
467impl Function for AsinFn {
468    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
469        self.signature.validate(args, ctx)?;
470        let n = args[0].as_f64().unwrap();
471        let result = n.asin();
472        // Return null for out-of-domain values (|n| > 1 produces NaN)
473        if result.is_nan() {
474            Ok(Value::Null)
475        } else {
476            Ok(number_value(result))
477        }
478    }
479}
480
481defn!(AcosFn, vec![arg!(number)], None);
482
483impl Function for AcosFn {
484    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
485        self.signature.validate(args, ctx)?;
486        let n = args[0].as_f64().unwrap();
487        let result = n.acos();
488        // Return null for out-of-domain values (|n| > 1 produces NaN)
489        if result.is_nan() {
490            Ok(Value::Null)
491        } else {
492            Ok(number_value(result))
493        }
494    }
495}
496
497defn!(AtanFn, vec![arg!(number)], None);
498
499impl Function for AtanFn {
500    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
501        self.signature.validate(args, ctx)?;
502        let n = args[0].as_f64().unwrap();
503        Ok(number_value(n.atan()))
504    }
505}
506
507defn!(Atan2Fn, vec![arg!(number), arg!(number)], None);
508
509impl Function for Atan2Fn {
510    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
511        self.signature.validate(args, ctx)?;
512        let y = args[0].as_f64().unwrap();
513        let x = args[1].as_f64().unwrap();
514        Ok(number_value(y.atan2(x)))
515    }
516}
517
518defn!(DegToRadFn, vec![arg!(number)], None);
519
520impl Function for DegToRadFn {
521    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
522        self.signature.validate(args, ctx)?;
523        let n = args[0].as_f64().unwrap();
524        Ok(number_value(n.to_radians()))
525    }
526}
527
528defn!(RadToDegFn, vec![arg!(number)], None);
529
530impl Function for RadToDegFn {
531    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
532        self.signature.validate(args, ctx)?;
533        let n = args[0].as_f64().unwrap();
534        Ok(number_value(n.to_degrees()))
535    }
536}
537
538defn!(SignFn, vec![arg!(number)], None);
539
540impl Function for SignFn {
541    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
542        self.signature.validate(args, ctx)?;
543        let n = args[0].as_f64().unwrap();
544        let sign = if n > 0.0 {
545            1
546        } else if n < 0.0 {
547            -1
548        } else {
549            0
550        };
551        Ok(Value::Number(Number::from(sign)))
552    }
553}
554
555// =============================================================================
556// add(a, b) -> number
557// =============================================================================
558
559defn!(AddFn, vec![arg!(number), arg!(number)], None);
560
561impl Function for AddFn {
562    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
563        self.signature.validate(args, ctx)?;
564        let a = args[0].as_f64().unwrap();
565        let b = args[1].as_f64().unwrap();
566        Ok(number_value(a + b))
567    }
568}
569
570// =============================================================================
571// subtract(a, b) -> number
572// =============================================================================
573
574defn!(SubtractFn, vec![arg!(number), arg!(number)], None);
575
576impl Function for SubtractFn {
577    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
578        self.signature.validate(args, ctx)?;
579        let a = args[0].as_f64().unwrap();
580        let b = args[1].as_f64().unwrap();
581        Ok(number_value(a - b))
582    }
583}
584
585// =============================================================================
586// multiply(a, b) -> number
587// =============================================================================
588
589defn!(MultiplyFn, vec![arg!(number), arg!(number)], None);
590
591impl Function for MultiplyFn {
592    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
593        self.signature.validate(args, ctx)?;
594        let a = args[0].as_f64().unwrap();
595        let b = args[1].as_f64().unwrap();
596        Ok(number_value(a * b))
597    }
598}
599
600// =============================================================================
601// divide(a, b) -> number
602// =============================================================================
603
604defn!(DivideFn, vec![arg!(number), arg!(number)], None);
605
606impl Function for DivideFn {
607    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
608        self.signature.validate(args, ctx)?;
609        let a = args[0].as_f64().unwrap();
610        let b = args[1].as_f64().unwrap();
611        if b == 0.0 {
612            return Err(crate::functions::custom_error(ctx, "Division by zero"));
613        }
614        Ok(number_value(a / b))
615    }
616}
617
618// =============================================================================
619// mode(array) -> any (most common value)
620// =============================================================================
621
622defn!(ModeFn, vec![arg!(array)], None);
623
624impl Function for ModeFn {
625    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
626        self.signature.validate(args, ctx)?;
627
628        let arr = args[0].as_array().unwrap();
629
630        if arr.is_empty() {
631            return Ok(Value::Null);
632        }
633
634        // Count occurrences - use JSON string representation as key
635        let mut counts: std::collections::HashMap<String, (usize, Value)> =
636            std::collections::HashMap::new();
637
638        for item in arr.iter() {
639            let key = serde_json::to_string(item).unwrap_or_default();
640            counts
641                .entry(key)
642                .and_modify(|(count, _)| *count += 1)
643                .or_insert((1, item.clone()));
644        }
645
646        // Find the value with highest count
647        let (_, (_, mode_value)) = counts
648            .into_iter()
649            .max_by_key(|(_, (count, _))| *count)
650            .unwrap();
651
652        Ok(mode_value)
653    }
654}
655
656// =============================================================================
657// to_fixed(number, precision) -> string
658// =============================================================================
659
660defn!(ToFixedFn, vec![arg!(number), arg!(number)], None);
661
662impl Function for ToFixedFn {
663    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
664        self.signature.validate(args, ctx)?;
665
666        let num = args[0].as_f64().unwrap();
667        let precision = args[1].as_f64().unwrap() as usize;
668
669        let result = format!("{:.prec$}", num, prec = precision);
670        Ok(Value::String(result))
671    }
672}
673
674// =============================================================================
675// format_number(number, precision?, suffix?) -> string
676// =============================================================================
677
678defn!(FormatNumberFn, vec![arg!(number)], Some(arg!(any)));
679
680impl Function for FormatNumberFn {
681    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
682        self.signature.validate(args, ctx)?;
683
684        let num = args[0].as_f64().unwrap();
685
686        let precision = args
687            .get(1)
688            .and_then(|v| v.as_f64())
689            .map(|n| n as usize)
690            .unwrap_or(0);
691
692        let suffix = args.get(2).and_then(|v| v.as_str()).map(|s| s.to_string());
693
694        // Handle suffix scaling (k, M, B, T)
695        let (scaled_num, auto_suffix) = if let Some(ref s) = suffix {
696            match s.as_str() {
697                "k" | "K" => (num / 1_000.0, "k"),
698                "M" => (num / 1_000_000.0, "M"),
699                "B" => (num / 1_000_000_000.0, "B"),
700                "T" => (num / 1_000_000_000_000.0, "T"),
701                "auto" => {
702                    let abs_num = num.abs();
703                    if abs_num >= 1_000_000_000_000.0 {
704                        (num / 1_000_000_000_000.0, "T")
705                    } else if abs_num >= 1_000_000_000.0 {
706                        (num / 1_000_000_000.0, "B")
707                    } else if abs_num >= 1_000_000.0 {
708                        (num / 1_000_000.0, "M")
709                    } else if abs_num >= 1_000.0 {
710                        (num / 1_000.0, "k")
711                    } else {
712                        (num, "")
713                    }
714                }
715                _ => (num, s.as_str()),
716            }
717        } else {
718            (num, "")
719        };
720
721        // Format with precision
722        let formatted = format!("{:.prec$}", scaled_num, prec = precision);
723
724        // Add thousand separators to the integer part
725        let result = if suffix.is_none() || suffix.as_deref() == Some("") {
726            add_thousand_separators(&formatted)
727        } else {
728            format!("{}{}", formatted, auto_suffix)
729        };
730
731        Ok(Value::String(result))
732    }
733}
734
735/// Add thousand separators (commas) to a number string.
736fn add_thousand_separators(s: &str) -> String {
737    let parts: Vec<&str> = s.split('.').collect();
738    let int_part = parts[0];
739    let dec_part = parts.get(1);
740
741    // Handle negative sign
742    let (sign, digits) = if let Some(stripped) = int_part.strip_prefix('-') {
743        ("-", stripped)
744    } else {
745        ("", int_part)
746    };
747
748    // Add commas every 3 digits from the right
749    let digit_chars: Vec<char> = digits.chars().collect();
750    let len = digit_chars.len();
751    let with_commas: String = digit_chars
752        .iter()
753        .enumerate()
754        .map(|(i, c)| {
755            let pos_from_right = len - 1 - i;
756            if pos_from_right > 0 && pos_from_right.is_multiple_of(3) {
757                format!("{},", c)
758            } else {
759                c.to_string()
760            }
761        })
762        .collect();
763
764    match dec_part {
765        Some(dec) => format!("{}{}.{}", sign, with_commas, dec),
766        None => format!("{}{}", sign, with_commas),
767    }
768}
769
770// =============================================================================
771// histogram(array, bins) -> array
772// =============================================================================
773
774defn!(HistogramFn, vec![arg!(array), arg!(number)], None);
775
776impl Function for HistogramFn {
777    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
778        self.signature.validate(args, ctx)?;
779
780        let arr = args[0].as_array().unwrap();
781
782        let num_bins = args[1].as_f64().unwrap() as usize;
783
784        if num_bins == 0 {
785            return Err(crate::functions::custom_error(
786                ctx,
787                "Number of bins must be greater than 0",
788            ));
789        }
790
791        // Extract numeric values
792        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
793
794        if values.is_empty() {
795            return Ok(Value::Array(vec![]));
796        }
797
798        let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
799        let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
800
801        // Handle case where all values are the same
802        let bin_width = if (max_val - min_val).abs() < f64::EPSILON {
803            1.0
804        } else {
805            (max_val - min_val) / num_bins as f64
806        };
807
808        // Initialize bins
809        let mut bins: Vec<(f64, f64, usize)> = (0..num_bins)
810            .map(|i| {
811                let bin_min = min_val + (i as f64 * bin_width);
812                let bin_max = if i == num_bins - 1 {
813                    max_val
814                } else {
815                    min_val + ((i + 1) as f64 * bin_width)
816                };
817                (bin_min, bin_max, 0)
818            })
819            .collect();
820
821        // Count values in each bin
822        for val in &values {
823            let bin_idx = if (max_val - min_val).abs() < f64::EPSILON {
824                0
825            } else {
826                let idx = ((val - min_val) / bin_width) as usize;
827                idx.min(num_bins - 1)
828            };
829            bins[bin_idx].2 += 1;
830        }
831
832        // Convert to array of objects
833        let result: Vec<Value> = bins
834            .into_iter()
835            .map(|(bin_min, bin_max, count)| {
836                let mut map = serde_json::Map::new();
837                map.insert("min".to_string(), number_value(bin_min));
838                map.insert("max".to_string(), number_value(bin_max));
839                map.insert("count".to_string(), Value::Number(Number::from(count)));
840                Value::Object(map)
841            })
842            .collect();
843
844        Ok(Value::Array(result))
845    }
846}
847
848// =============================================================================
849// normalize(array) -> array
850// =============================================================================
851
852defn!(NormalizeFn, vec![arg!(array)], None);
853
854impl Function for NormalizeFn {
855    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
856        self.signature.validate(args, ctx)?;
857
858        let arr = args[0].as_array().unwrap();
859
860        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
861
862        if values.is_empty() {
863            return Ok(Value::Array(vec![]));
864        }
865
866        let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
867        let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
868        let range = max_val - min_val;
869
870        let result: Vec<Value> = values
871            .iter()
872            .map(|v| {
873                let normalized = if range.abs() < f64::EPSILON {
874                    0.0
875                } else {
876                    (v - min_val) / range
877                };
878                number_value(normalized)
879            })
880            .collect();
881
882        Ok(Value::Array(result))
883    }
884}
885
886// =============================================================================
887// z_score(array) -> array
888// =============================================================================
889
890defn!(ZScoreFn, vec![arg!(array)], None);
891
892impl Function for ZScoreFn {
893    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
894        self.signature.validate(args, ctx)?;
895
896        let arr = args[0].as_array().unwrap();
897
898        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
899
900        if values.is_empty() {
901            return Ok(Value::Array(vec![]));
902        }
903
904        let n = values.len() as f64;
905        let mean = values.iter().sum::<f64>() / n;
906        let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
907        let stddev = variance.sqrt();
908
909        let result: Vec<Value> = values
910            .iter()
911            .map(|v| {
912                let z = if stddev.abs() < f64::EPSILON {
913                    0.0
914                } else {
915                    (v - mean) / stddev
916                };
917                number_value(z)
918            })
919            .collect();
920
921        Ok(Value::Array(result))
922    }
923}
924
925// =============================================================================
926// correlation(arr1, arr2) -> number
927// =============================================================================
928
929defn!(CorrelationFn, vec![arg!(array), arg!(array)], None);
930
931impl Function for CorrelationFn {
932    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
933        self.signature.validate(args, ctx)?;
934
935        let arr1 = args[0].as_array().unwrap();
936        let arr2 = args[1].as_array().unwrap();
937
938        let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_f64()).collect();
939        let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_f64()).collect();
940
941        if values1.is_empty() || values2.is_empty() {
942            return Ok(Value::Null);
943        }
944
945        // Use the shorter length
946        let n = values1.len().min(values2.len());
947        if n == 0 {
948            return Ok(Value::Null);
949        }
950
951        let values1 = &values1[..n];
952        let values2 = &values2[..n];
953
954        let mean1 = values1.iter().sum::<f64>() / n as f64;
955        let mean2 = values2.iter().sum::<f64>() / n as f64;
956
957        let mut cov = 0.0;
958        let mut var1 = 0.0;
959        let mut var2 = 0.0;
960
961        for i in 0..n {
962            let d1 = values1[i] - mean1;
963            let d2 = values2[i] - mean2;
964            cov += d1 * d2;
965            var1 += d1 * d1;
966            var2 += d2 * d2;
967        }
968
969        let denom = (var1 * var2).sqrt();
970        let correlation = if denom.abs() < f64::EPSILON {
971            0.0
972        } else {
973            cov / denom
974        };
975
976        Ok(number_value(correlation))
977    }
978}
979
980// =============================================================================
981// quantile(array, q) -> number (q in [0, 1])
982// =============================================================================
983
984defn!(QuantileFn, vec![arg!(array), arg!(number)], None);
985
986impl Function for QuantileFn {
987    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
988        self.signature.validate(args, ctx)?;
989
990        let arr = args[0].as_array().unwrap();
991        let q = args[1].as_f64().unwrap();
992
993        if !(0.0..=1.0).contains(&q) {
994            return Ok(Value::Null);
995        }
996
997        let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
998
999        if values.is_empty() {
1000            return Ok(Value::Null);
1001        }
1002
1003        values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1004
1005        let n = values.len();
1006        let pos = q * (n - 1) as f64;
1007        let lower = pos.floor() as usize;
1008        let upper = pos.ceil() as usize;
1009        let frac = pos - lower as f64;
1010
1011        let result = if lower == upper {
1012            values[lower]
1013        } else {
1014            values[lower] * (1.0 - frac) + values[upper] * frac
1015        };
1016
1017        Ok(number_value(result))
1018    }
1019}
1020
1021// =============================================================================
1022// moving_avg(array, window) -> array
1023// =============================================================================
1024
1025defn!(MovingAvgFn, vec![arg!(array), arg!(number)], None);
1026
1027impl Function for MovingAvgFn {
1028    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1029        self.signature.validate(args, ctx)?;
1030
1031        let arr = args[0].as_array().unwrap();
1032
1033        let window = args[1].as_f64().unwrap() as usize;
1034
1035        if window == 0 {
1036            return Ok(Value::Null);
1037        }
1038
1039        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1040
1041        if values.is_empty() || window > values.len() {
1042            return Ok(Value::Array(vec![]));
1043        }
1044
1045        let mut result: Vec<Value> = Vec::new();
1046
1047        for i in 0..values.len() {
1048            if i + 1 < window {
1049                result.push(Value::Null);
1050            } else {
1051                let start = i + 1 - window;
1052                let sum: f64 = values[start..=i].iter().sum();
1053                let avg = sum / window as f64;
1054                result.push(number_value(avg));
1055            }
1056        }
1057
1058        Ok(Value::Array(result))
1059    }
1060}
1061
1062// =============================================================================
1063// ewma(array, alpha) -> array
1064// =============================================================================
1065
1066defn!(EwmaFn, vec![arg!(array), arg!(number)], None);
1067
1068impl Function for EwmaFn {
1069    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1070        self.signature.validate(args, ctx)?;
1071
1072        let arr = args[0].as_array().unwrap();
1073        let alpha = args[1].as_f64().unwrap();
1074
1075        if !(0.0..=1.0).contains(&alpha) {
1076            return Ok(Value::Null);
1077        }
1078
1079        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1080
1081        if values.is_empty() {
1082            return Ok(Value::Array(vec![]));
1083        }
1084
1085        let mut result: Vec<Value> = Vec::new();
1086        let mut ewma = values[0];
1087
1088        for value in &values {
1089            ewma = alpha * value + (1.0 - alpha) * ewma;
1090            result.push(number_value(ewma));
1091        }
1092
1093        Ok(Value::Array(result))
1094    }
1095}
1096
1097// =============================================================================
1098// covariance(arr1, arr2) -> number
1099// =============================================================================
1100
1101defn!(CovarianceFn, vec![arg!(array), arg!(array)], None);
1102
1103impl Function for CovarianceFn {
1104    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1105        self.signature.validate(args, ctx)?;
1106
1107        let arr1 = args[0].as_array().unwrap();
1108        let arr2 = args[1].as_array().unwrap();
1109
1110        let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_f64()).collect();
1111        let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_f64()).collect();
1112
1113        if values1.is_empty() || values1.len() != values2.len() {
1114            return Ok(Value::Null);
1115        }
1116
1117        let n = values1.len() as f64;
1118        let mean1: f64 = values1.iter().sum::<f64>() / n;
1119        let mean2: f64 = values2.iter().sum::<f64>() / n;
1120
1121        let cov: f64 = values1
1122            .iter()
1123            .zip(values2.iter())
1124            .map(|(x, y)| (x - mean1) * (y - mean2))
1125            .sum::<f64>()
1126            / n;
1127
1128        Ok(number_value(cov))
1129    }
1130}
1131
1132// =============================================================================
1133// standardize(array) -> array (z-score normalization)
1134// =============================================================================
1135
1136defn!(StandardizeFn, vec![arg!(array)], None);
1137
1138impl Function for StandardizeFn {
1139    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1140        self.signature.validate(args, ctx)?;
1141
1142        let arr = args[0].as_array().unwrap();
1143
1144        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1145
1146        if values.is_empty() {
1147            return Ok(Value::Array(vec![]));
1148        }
1149
1150        let n = values.len() as f64;
1151        let mean: f64 = values.iter().sum::<f64>() / n;
1152        let variance: f64 = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n;
1153        let std_dev = variance.sqrt();
1154
1155        let result: Vec<Value> = values
1156            .iter()
1157            .map(|x| {
1158                let standardized = if std_dev.abs() < f64::EPSILON {
1159                    0.0
1160                } else {
1161                    (x - mean) / std_dev
1162                };
1163                number_value(standardized)
1164            })
1165            .collect();
1166
1167        Ok(Value::Array(result))
1168    }
1169}
1170
1171// =============================================================================
1172// quartiles(array) -> object {q1, q2, q3, min, max, iqr}
1173// =============================================================================
1174
1175defn!(QuartilesFn, vec![arg!(array)], None);
1176
1177impl Function for QuartilesFn {
1178    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1179        self.signature.validate(args, ctx)?;
1180
1181        let arr = args[0].as_array().unwrap();
1182
1183        let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1184
1185        if values.is_empty() {
1186            return Ok(Value::Null);
1187        }
1188
1189        values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1190
1191        let n = values.len();
1192        let min = values[0];
1193        let max = values[n - 1];
1194
1195        let q1 = percentile_value(&values, 25.0);
1196        let q2 = percentile_value(&values, 50.0);
1197        let q3 = percentile_value(&values, 75.0);
1198        let iqr = q3 - q1;
1199
1200        let mut result = serde_json::Map::new();
1201        result.insert("min".to_string(), number_value(min));
1202        result.insert("q1".to_string(), number_value(q1));
1203        result.insert("q2".to_string(), number_value(q2));
1204        result.insert("q3".to_string(), number_value(q3));
1205        result.insert("max".to_string(), number_value(max));
1206        result.insert("iqr".to_string(), number_value(iqr));
1207
1208        Ok(Value::Object(result))
1209    }
1210}
1211
1212fn percentile_value(sorted_values: &[f64], p: f64) -> f64 {
1213    let n = sorted_values.len();
1214    if n == 1 {
1215        return sorted_values[0];
1216    }
1217
1218    let k = (p / 100.0) * (n - 1) as f64;
1219    let f = k.floor() as usize;
1220    let c = k.ceil() as usize;
1221
1222    if f == c {
1223        sorted_values[f]
1224    } else {
1225        let d = k - f as f64;
1226        sorted_values[f] * (1.0 - d) + sorted_values[c] * d
1227    }
1228}
1229
1230// =============================================================================
1231// outliers_iqr(array, multiplier?) -> array
1232// =============================================================================
1233
1234defn!(OutliersIqrFn, vec![arg!(array)], Some(arg!(number)));
1235
1236impl Function for OutliersIqrFn {
1237    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1238        self.signature.validate(args, ctx)?;
1239
1240        let arr = args[0].as_array().unwrap();
1241
1242        let multiplier = if args.len() > 1 {
1243            args[1].as_f64().unwrap_or(1.5)
1244        } else {
1245            1.5
1246        };
1247
1248        let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1249
1250        if values.is_empty() {
1251            return Ok(Value::Array(vec![]));
1252        }
1253
1254        values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1255
1256        let q1 = percentile_value(&values, 25.0);
1257        let q3 = percentile_value(&values, 75.0);
1258        let iqr = q3 - q1;
1259
1260        let lower_bound = q1 - multiplier * iqr;
1261        let upper_bound = q3 + multiplier * iqr;
1262
1263        let outliers: Vec<Value> = arr
1264            .iter()
1265            .filter(|v| {
1266                if let Some(n) = v.as_f64() {
1267                    n < lower_bound || n > upper_bound
1268                } else {
1269                    false
1270                }
1271            })
1272            .cloned()
1273            .collect();
1274
1275        Ok(Value::Array(outliers))
1276    }
1277}
1278
1279// =============================================================================
1280// outliers_zscore(array, threshold?) -> array
1281// =============================================================================
1282
1283defn!(OutliersZscoreFn, vec![arg!(array)], Some(arg!(number)));
1284
1285impl Function for OutliersZscoreFn {
1286    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1287        self.signature.validate(args, ctx)?;
1288
1289        let arr = args[0].as_array().unwrap();
1290
1291        let threshold = if args.len() > 1 {
1292            args[1].as_f64().unwrap_or(2.0)
1293        } else {
1294            2.0
1295        };
1296
1297        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1298
1299        if values.is_empty() {
1300            return Ok(Value::Array(vec![]));
1301        }
1302
1303        let n = values.len() as f64;
1304        let mean: f64 = values.iter().sum::<f64>() / n;
1305        let variance: f64 = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
1306        let stddev = variance.sqrt();
1307
1308        if stddev.abs() < f64::EPSILON {
1309            return Ok(Value::Array(vec![]));
1310        }
1311
1312        let outliers: Vec<Value> = arr
1313            .iter()
1314            .filter(|v| {
1315                if let Some(n) = v.as_f64() {
1316                    let z = (n - mean) / stddev;
1317                    z.abs() > threshold
1318                } else {
1319                    false
1320                }
1321            })
1322            .cloned()
1323            .collect();
1324
1325        Ok(Value::Array(outliers))
1326    }
1327}
1328
1329// =============================================================================
1330// trend(array) -> string ("increasing", "decreasing", "stable")
1331// =============================================================================
1332
1333defn!(TrendFn, vec![arg!(array)], None);
1334
1335impl Function for TrendFn {
1336    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1337        self.signature.validate(args, ctx)?;
1338
1339        let arr = args[0].as_array().unwrap();
1340
1341        if arr.len() < 2 {
1342            return Ok(Value::String("stable".to_string()));
1343        }
1344
1345        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1346
1347        if values.len() < 2 {
1348            return Ok(Value::String("stable".to_string()));
1349        }
1350
1351        // Calculate linear regression slope
1352        let n = values.len() as f64;
1353        let sum_x: f64 = (0..values.len()).map(|i| i as f64).sum();
1354        let sum_y: f64 = values.iter().sum();
1355        let sum_xy: f64 = values.iter().enumerate().map(|(i, y)| i as f64 * y).sum();
1356        let sum_x2: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
1357
1358        let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x.powi(2));
1359
1360        // Determine trend based on slope relative to data magnitude
1361        let mean: f64 = sum_y / n;
1362        let threshold = mean.abs() * 0.01;
1363
1364        let trend = if slope > threshold {
1365            "increasing"
1366        } else if slope < -threshold {
1367            "decreasing"
1368        } else {
1369            "stable"
1370        };
1371
1372        Ok(Value::String(trend.to_string()))
1373    }
1374}
1375
1376// =============================================================================
1377// trend_slope(array) -> number (linear regression slope)
1378// =============================================================================
1379
1380defn!(TrendSlopeFn, vec![arg!(array)], None);
1381
1382impl Function for TrendSlopeFn {
1383    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1384        self.signature.validate(args, ctx)?;
1385
1386        let arr = args[0].as_array().unwrap();
1387
1388        if arr.len() < 2 {
1389            return Ok(number_value(0.0));
1390        }
1391
1392        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1393
1394        if values.len() < 2 {
1395            return Ok(number_value(0.0));
1396        }
1397
1398        // Calculate linear regression slope
1399        let n = values.len() as f64;
1400        let sum_x: f64 = (0..values.len()).map(|i| i as f64).sum();
1401        let sum_y: f64 = values.iter().sum();
1402        let sum_xy: f64 = values.iter().enumerate().map(|(i, y)| i as f64 * y).sum();
1403        let sum_x2: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
1404
1405        let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x.powi(2));
1406
1407        Ok(number_value(slope))
1408    }
1409}
1410
1411// =============================================================================
1412// rate_of_change(array) -> array (percentage change between consecutive elements)
1413// =============================================================================
1414
1415defn!(RateOfChangeFn, vec![arg!(array)], None);
1416
1417impl Function for RateOfChangeFn {
1418    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1419        self.signature.validate(args, ctx)?;
1420
1421        let arr = args[0].as_array().unwrap();
1422
1423        if arr.len() < 2 {
1424            return Ok(Value::Array(vec![]));
1425        }
1426
1427        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1428
1429        if values.len() < 2 {
1430            return Ok(Value::Array(vec![]));
1431        }
1432
1433        let changes: Vec<Value> = values
1434            .windows(2)
1435            .map(|w| {
1436                let prev = w[0];
1437                let curr = w[1];
1438                let pct_change = if prev != 0.0 {
1439                    ((curr - prev) / prev) * 100.0
1440                } else {
1441                    0.0
1442                };
1443                number_value(pct_change)
1444            })
1445            .collect();
1446
1447        Ok(Value::Array(changes))
1448    }
1449}
1450
1451// =============================================================================
1452// cumulative_sum(array) -> array (running total)
1453// =============================================================================
1454
1455defn!(CumulativeSumFn, vec![arg!(array)], None);
1456
1457impl Function for CumulativeSumFn {
1458    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1459        self.signature.validate(args, ctx)?;
1460
1461        let arr = args[0].as_array().unwrap();
1462
1463        let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1464
1465        let mut running_sum = 0.0;
1466        let cumsum: Vec<Value> = values
1467            .iter()
1468            .map(|v| {
1469                running_sum += v;
1470                number_value(running_sum)
1471            })
1472            .collect();
1473
1474        Ok(Value::Array(cumsum))
1475    }
1476}
1477
1478// =============================================================================
1479// skew(array) -> number (Fisher-Pearson coefficient of skewness)
1480// =============================================================================
1481
1482defn!(SkewFn, vec![arg!(array)], None);
1483
1484impl Function for SkewFn {
1485    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1486        self.signature.validate(args, ctx)?;
1487        let arr = args[0].as_array().unwrap();
1488        let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1489        let n = numbers.len();
1490        if n < 3 {
1491            return Ok(Value::Null);
1492        }
1493        let mean = numbers.iter().sum::<f64>() / n as f64;
1494        let m2: f64 = numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n as f64;
1495        let m3: f64 = numbers.iter().map(|x| (x - mean).powi(3)).sum::<f64>() / n as f64;
1496        if m2 == 0.0 {
1497            return Ok(number_value(0.0));
1498        }
1499        let skewness = m3 / m2.powf(1.5);
1500        Ok(number_value(skewness))
1501    }
1502}
1503
1504// =============================================================================
1505// kurtosis(array) -> number (excess kurtosis: normal distribution = 0)
1506// =============================================================================
1507
1508defn!(KurtosisFn, vec![arg!(array)], None);
1509
1510impl Function for KurtosisFn {
1511    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1512        self.signature.validate(args, ctx)?;
1513        let arr = args[0].as_array().unwrap();
1514        let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1515        let n = numbers.len();
1516        if n < 4 {
1517            return Ok(Value::Null);
1518        }
1519        let mean = numbers.iter().sum::<f64>() / n as f64;
1520        let m2: f64 = numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n as f64;
1521        let m4: f64 = numbers.iter().map(|x| (x - mean).powi(4)).sum::<f64>() / n as f64;
1522        if m2 == 0.0 {
1523            return Ok(number_value(0.0));
1524        }
1525        let kurt = (m4 / (m2 * m2)) - 3.0;
1526        Ok(number_value(kurt))
1527    }
1528}
1529
1530// =============================================================================
1531// mad(array) -> number (median absolute deviation)
1532// =============================================================================
1533
1534defn!(MadFn, vec![arg!(array)], None);
1535
1536impl Function for MadFn {
1537    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1538        self.signature.validate(args, ctx)?;
1539        let arr = args[0].as_array().unwrap();
1540        let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1541        if numbers.is_empty() {
1542            return Ok(Value::Null);
1543        }
1544        numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1545        let len = numbers.len();
1546        let median = if len.is_multiple_of(2) {
1547            (numbers[len / 2 - 1] + numbers[len / 2]) / 2.0
1548        } else {
1549            numbers[len / 2]
1550        };
1551        let mut abs_devs: Vec<f64> = numbers.iter().map(|x| (x - median).abs()).collect();
1552        abs_devs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1553        let mad = if len.is_multiple_of(2) {
1554            (abs_devs[len / 2 - 1] + abs_devs[len / 2]) / 2.0
1555        } else {
1556            abs_devs[len / 2]
1557        };
1558        Ok(number_value(mad))
1559    }
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564    use crate::Runtime;
1565    use serde_json::json;
1566
1567    fn setup_runtime() -> Runtime {
1568        Runtime::builder()
1569            .with_standard()
1570            .with_all_extensions()
1571            .build()
1572    }
1573
1574    #[test]
1575    #[allow(clippy::approx_constant)]
1576    fn test_round() {
1577        let runtime = setup_runtime();
1578        let expr = runtime.compile("round(`3.14159`, `2`)").unwrap();
1579        let result = expr.search(&json!(null)).unwrap();
1580        assert!((result.as_f64().unwrap() - 3.14_f64).abs() < 0.001);
1581    }
1582
1583    #[test]
1584    fn test_sqrt() {
1585        let runtime = setup_runtime();
1586        let expr = runtime.compile("sqrt(`16`)").unwrap();
1587        let result = expr.search(&json!(null)).unwrap();
1588        assert_eq!(result.as_f64().unwrap() as i64, 4);
1589    }
1590
1591    #[test]
1592    fn test_clamp() {
1593        let runtime = setup_runtime();
1594        let expr = runtime.compile("clamp(`5`, `0`, `3`)").unwrap();
1595        let result = expr.search(&json!(null)).unwrap();
1596        assert_eq!(result.as_f64().unwrap() as i64, 3);
1597    }
1598
1599    #[test]
1600    fn test_add() {
1601        let runtime = setup_runtime();
1602        let expr = runtime.compile("add(`1`, `2`)").unwrap();
1603        let result = expr.search(&json!(null)).unwrap();
1604        assert_eq!(result.as_f64().unwrap() as i64, 3);
1605    }
1606
1607    #[test]
1608    fn test_subtract() {
1609        let runtime = setup_runtime();
1610        let expr = runtime.compile("subtract(`10`, `3`)").unwrap();
1611        let result = expr.search(&json!(null)).unwrap();
1612        assert_eq!(result.as_f64().unwrap() as i64, 7);
1613    }
1614
1615    #[test]
1616    fn test_multiply() {
1617        let runtime = setup_runtime();
1618        let expr = runtime.compile("multiply(`4`, `5`)").unwrap();
1619        let result = expr.search(&json!(null)).unwrap();
1620        assert_eq!(result.as_f64().unwrap() as i64, 20);
1621    }
1622
1623    #[test]
1624    fn test_divide() {
1625        let runtime = setup_runtime();
1626        let expr = runtime.compile("divide(`10`, `4`)").unwrap();
1627        let result = expr.search(&json!(null)).unwrap();
1628        assert_eq!(result.as_f64().unwrap(), 2.5);
1629    }
1630
1631    #[test]
1632    fn test_mode_numbers() {
1633        let runtime = setup_runtime();
1634        let expr = runtime.compile("mode(`[1, 2, 2, 3]`)").unwrap();
1635        let result = expr.search(&json!(null)).unwrap();
1636        assert_eq!(result.as_f64().unwrap() as i64, 2);
1637    }
1638
1639    #[test]
1640    fn test_mode_strings() {
1641        let runtime = setup_runtime();
1642        let expr = runtime
1643            .compile("mode(`[\"a\", \"b\", \"a\", \"c\"]`)")
1644            .unwrap();
1645        let result = expr.search(&json!(null)).unwrap();
1646        assert_eq!(result.as_str().unwrap(), "a");
1647    }
1648
1649    #[test]
1650    fn test_mode_empty() {
1651        let runtime = setup_runtime();
1652        let expr = runtime.compile("mode(`[]`)").unwrap();
1653        let result = expr.search(&json!(null)).unwrap();
1654        assert!(result.is_null());
1655    }
1656
1657    #[test]
1658    fn test_to_fixed() {
1659        let runtime = setup_runtime();
1660        let expr = runtime.compile("to_fixed(`3.14159`, `2`)").unwrap();
1661        let result = expr.search(&json!(null)).unwrap();
1662        assert_eq!(result.as_str().unwrap(), "3.14");
1663    }
1664
1665    #[test]
1666    fn test_to_fixed_padding() {
1667        let runtime = setup_runtime();
1668        let expr = runtime.compile("to_fixed(`3.1`, `3`)").unwrap();
1669        let result = expr.search(&json!(null)).unwrap();
1670        assert_eq!(result.as_str().unwrap(), "3.100");
1671    }
1672
1673    #[test]
1674    fn test_format_number_with_separators() {
1675        let runtime = setup_runtime();
1676        let expr = runtime.compile("format_number(`1234567.89`, `2`)").unwrap();
1677        let result = expr.search(&json!(null)).unwrap();
1678        assert_eq!(result.as_str().unwrap(), "1,234,567.89");
1679    }
1680
1681    #[test]
1682    fn test_format_number_with_k_suffix() {
1683        let runtime = setup_runtime();
1684        let expr = runtime.compile("format_number(`1500`, `1`, 'k')").unwrap();
1685        let result = expr.search(&json!(null)).unwrap();
1686        assert_eq!(result.as_str().unwrap(), "1.5k");
1687    }
1688
1689    #[test]
1690    fn test_format_number_with_m_suffix() {
1691        let runtime = setup_runtime();
1692        let expr = runtime
1693            .compile("format_number(`1500000`, `1`, 'M')")
1694            .unwrap();
1695        let result = expr.search(&json!(null)).unwrap();
1696        assert_eq!(result.as_str().unwrap(), "1.5M");
1697    }
1698
1699    #[test]
1700    fn test_format_number_auto_suffix() {
1701        let runtime = setup_runtime();
1702        let expr = runtime
1703            .compile("format_number(`1500000000`, `2`, 'auto')")
1704            .unwrap();
1705        let result = expr.search(&json!(null)).unwrap();
1706        assert_eq!(result.as_str().unwrap(), "1.50B");
1707    }
1708
1709    #[test]
1710    fn test_histogram() {
1711        let runtime = setup_runtime();
1712        let expr = runtime.compile("histogram(@, `3`)").unwrap();
1713        let data = json!([1, 2, 3, 4, 5, 6, 7, 8, 9]);
1714        let result = expr.search(&data).unwrap();
1715        let arr = result.as_array().unwrap();
1716        assert_eq!(arr.len(), 3);
1717        // Each bin should have 3 values
1718        for bin in arr {
1719            let obj = bin.as_object().unwrap();
1720            assert!(obj.contains_key("min"));
1721            assert!(obj.contains_key("max"));
1722            assert!(obj.contains_key("count"));
1723        }
1724    }
1725
1726    #[test]
1727    fn test_normalize() {
1728        let runtime = setup_runtime();
1729        let expr = runtime.compile("normalize(@)").unwrap();
1730        let data = json!([0, 50, 100]);
1731        let result = expr.search(&data).unwrap();
1732        let arr = result.as_array().unwrap();
1733        assert_eq!(arr.len(), 3);
1734        assert!((arr[0].as_f64().unwrap() - 0.0).abs() < 0.001);
1735        assert!((arr[1].as_f64().unwrap() - 0.5).abs() < 0.001);
1736        assert!((arr[2].as_f64().unwrap() - 1.0).abs() < 0.001);
1737    }
1738
1739    #[test]
1740    fn test_z_score() {
1741        let runtime = setup_runtime();
1742        let expr = runtime.compile("z_score(@)").unwrap();
1743        let data = json!([1, 2, 3, 4, 5]);
1744        let result = expr.search(&data).unwrap();
1745        let arr = result.as_array().unwrap();
1746        assert_eq!(arr.len(), 5);
1747        // Middle value (3) should have z-score of 0
1748        assert!((arr[2].as_f64().unwrap() - 0.0).abs() < 0.001);
1749    }
1750
1751    #[test]
1752    fn test_correlation_positive() {
1753        let runtime = setup_runtime();
1754        let expr = runtime
1755            .compile("correlation(`[1, 2, 3]`, `[1, 2, 3]`)")
1756            .unwrap();
1757        let result = expr.search(&json!(null)).unwrap();
1758        assert!((result.as_f64().unwrap() - 1.0).abs() < 0.001);
1759    }
1760
1761    #[test]
1762    fn test_correlation_negative() {
1763        let runtime = setup_runtime();
1764        let expr = runtime
1765            .compile("correlation(`[1, 2, 3]`, `[3, 2, 1]`)")
1766            .unwrap();
1767        let result = expr.search(&json!(null)).unwrap();
1768        assert!((result.as_f64().unwrap() - (-1.0)).abs() < 0.001);
1769    }
1770
1771    #[test]
1772    fn test_quantile_median() {
1773        let runtime = setup_runtime();
1774        let expr = runtime
1775            .compile("quantile(`[1, 2, 3, 4, 5]`, `0.5`)")
1776            .unwrap();
1777        let result = expr.search(&json!(null)).unwrap();
1778        assert_eq!(result.as_f64().unwrap(), 3.0);
1779    }
1780
1781    #[test]
1782    fn test_quantile_quartiles() {
1783        let runtime = setup_runtime();
1784        // First quartile
1785        let expr = runtime
1786            .compile("quantile(`[1, 2, 3, 4, 5]`, `0.25`)")
1787            .unwrap();
1788        let result = expr.search(&json!(null)).unwrap();
1789        assert_eq!(result.as_f64().unwrap(), 2.0);
1790
1791        // Third quartile
1792        let expr = runtime
1793            .compile("quantile(`[1, 2, 3, 4, 5]`, `0.75`)")
1794            .unwrap();
1795        let result = expr.search(&json!(null)).unwrap();
1796        assert_eq!(result.as_f64().unwrap(), 4.0);
1797    }
1798
1799    #[test]
1800    fn test_moving_avg() {
1801        let runtime = setup_runtime();
1802        let expr = runtime
1803            .compile("moving_avg(`[1, 2, 3, 4, 5, 6]`, `3`)")
1804            .unwrap();
1805        let result = expr.search(&json!(null)).unwrap();
1806        let arr = result.as_array().unwrap();
1807        assert_eq!(arr.len(), 6);
1808        assert!(arr[0].is_null());
1809        assert!(arr[1].is_null());
1810        assert_eq!(arr[2].as_f64().unwrap(), 2.0); // (1+2+3)/3
1811        assert_eq!(arr[3].as_f64().unwrap(), 3.0); // (2+3+4)/3
1812        assert_eq!(arr[4].as_f64().unwrap(), 4.0); // (3+4+5)/3
1813        assert_eq!(arr[5].as_f64().unwrap(), 5.0); // (4+5+6)/3
1814    }
1815
1816    #[test]
1817    fn test_ewma() {
1818        let runtime = setup_runtime();
1819        let expr = runtime.compile("ewma(`[1, 2, 3, 4, 5]`, `0.5`)").unwrap();
1820        let result = expr.search(&json!(null)).unwrap();
1821        let arr = result.as_array().unwrap();
1822        assert_eq!(arr.len(), 5);
1823        // First value is just the first value
1824        assert_eq!(arr[0].as_f64().unwrap(), 1.0);
1825        // Subsequent values: alpha * current + (1-alpha) * prev_ewma
1826        assert_eq!(arr[1].as_f64().unwrap(), 1.5); // 0.5*2 + 0.5*1
1827        assert_eq!(arr[2].as_f64().unwrap(), 2.25); // 0.5*3 + 0.5*1.5
1828    }
1829
1830    #[test]
1831    fn test_covariance() {
1832        let runtime = setup_runtime();
1833        let expr = runtime
1834            .compile("covariance(`[1, 2, 3]`, `[1, 2, 3]`)")
1835            .unwrap();
1836        let result = expr.search(&json!(null)).unwrap();
1837        // Variance of [1,2,3] is 2/3
1838        assert!((result.as_f64().unwrap() - 0.666666).abs() < 0.01);
1839    }
1840
1841    #[test]
1842    fn test_covariance_negative() {
1843        let runtime = setup_runtime();
1844        let expr = runtime
1845            .compile("covariance(`[1, 2, 3]`, `[3, 2, 1]`)")
1846            .unwrap();
1847        let result = expr.search(&json!(null)).unwrap();
1848        assert!((result.as_f64().unwrap() - (-0.666666)).abs() < 0.01);
1849    }
1850
1851    #[test]
1852    fn test_standardize() {
1853        let runtime = setup_runtime();
1854        let expr = runtime
1855            .compile("standardize(`[10, 20, 30, 40, 50]`)")
1856            .unwrap();
1857        let result = expr.search(&json!(null)).unwrap();
1858        let arr = result.as_array().unwrap();
1859        assert_eq!(arr.len(), 5);
1860        // Mean is 30, std is ~14.14
1861        // First value: (10-30)/14.14 ~ -1.41
1862        assert!((arr[0].as_f64().unwrap() - (-1.414)).abs() < 0.01);
1863        // Middle value should be 0
1864        assert!(arr[2].as_f64().unwrap().abs() < 0.001);
1865        // Last value: (50-30)/14.14 ~ 1.41
1866        assert!((arr[4].as_f64().unwrap() - 1.414).abs() < 0.01);
1867    }
1868
1869    #[test]
1870    fn test_trend_increasing() {
1871        let runtime = setup_runtime();
1872        let expr = runtime.compile("trend(`[1, 2, 3, 5, 8]`)").unwrap();
1873        let result = expr.search(&json!(null)).unwrap();
1874        assert_eq!(result.as_str().unwrap(), "increasing");
1875    }
1876
1877    #[test]
1878    fn test_trend_decreasing() {
1879        let runtime = setup_runtime();
1880        let expr = runtime.compile("trend(`[10, 9, 8, 7, 6]`)").unwrap();
1881        let result = expr.search(&json!(null)).unwrap();
1882        assert_eq!(result.as_str().unwrap(), "decreasing");
1883    }
1884
1885    #[test]
1886    fn test_trend_stable() {
1887        let runtime = setup_runtime();
1888        let expr = runtime.compile("trend(`[5, 5, 5, 5, 5]`)").unwrap();
1889        let result = expr.search(&json!(null)).unwrap();
1890        assert_eq!(result.as_str().unwrap(), "stable");
1891    }
1892
1893    #[test]
1894    fn test_trend_slope() {
1895        let runtime = setup_runtime();
1896        let expr = runtime.compile("trend_slope(`[0, 1, 2, 3, 4]`)").unwrap();
1897        let result = expr.search(&json!(null)).unwrap();
1898        // Perfect linear increase with slope 1
1899        assert!((result.as_f64().unwrap() - 1.0).abs() < 0.001);
1900    }
1901
1902    #[test]
1903    fn test_rate_of_change() {
1904        let runtime = setup_runtime();
1905        let expr = runtime
1906            .compile("rate_of_change(`[100, 110, 105]`)")
1907            .unwrap();
1908        let result = expr.search(&json!(null)).unwrap();
1909        let arr = result.as_array().unwrap();
1910        assert_eq!(arr.len(), 2);
1911        // 100 -> 110 = 10% increase
1912        assert!((arr[0].as_f64().unwrap() - 10.0).abs() < 0.01);
1913        // 110 -> 105 = -4.545% decrease
1914        assert!((arr[1].as_f64().unwrap() - (-4.545)).abs() < 0.01);
1915    }
1916
1917    #[test]
1918    fn test_cumulative_sum() {
1919        let runtime = setup_runtime();
1920        let expr = runtime.compile("cumulative_sum(`[1, 2, 3, 4]`)").unwrap();
1921        let result = expr.search(&json!(null)).unwrap();
1922        let arr = result.as_array().unwrap();
1923        assert_eq!(arr.len(), 4);
1924        assert_eq!(arr[0].as_f64().unwrap(), 1.0);
1925        assert_eq!(arr[1].as_f64().unwrap(), 3.0);
1926        assert_eq!(arr[2].as_f64().unwrap(), 6.0);
1927        assert_eq!(arr[3].as_f64().unwrap(), 10.0);
1928    }
1929
1930    #[test]
1931    fn test_skew_symmetric() {
1932        let runtime = setup_runtime();
1933        let expr = runtime.compile("skew(`[1, 2, 3, 4, 5]`)").unwrap();
1934        let result = expr.search(&json!(null)).unwrap();
1935        assert!((result.as_f64().unwrap() - 0.0).abs() < 0.001);
1936    }
1937
1938    #[test]
1939    fn test_skew_right() {
1940        let runtime = setup_runtime();
1941        let expr = runtime.compile("skew(`[1, 1, 1, 10]`)").unwrap();
1942        let result = expr.search(&json!(null)).unwrap();
1943        // Right-skewed: positive skewness
1944        assert!(result.as_f64().unwrap() > 0.0);
1945    }
1946
1947    #[test]
1948    fn test_skew_too_few() {
1949        let runtime = setup_runtime();
1950        let expr = runtime.compile("skew(`[1, 2]`)").unwrap();
1951        let result = expr.search(&json!(null)).unwrap();
1952        assert!(result.is_null());
1953    }
1954
1955    #[test]
1956    fn test_skew_constant() {
1957        let runtime = setup_runtime();
1958        let expr = runtime.compile("skew(`[5, 5, 5, 5]`)").unwrap();
1959        let result = expr.search(&json!(null)).unwrap();
1960        assert_eq!(result.as_f64().unwrap(), 0.0);
1961    }
1962
1963    #[test]
1964    fn test_kurtosis_uniform_like() {
1965        let runtime = setup_runtime();
1966        let expr = runtime.compile("kurtosis(`[1, 2, 3, 4, 5]`)").unwrap();
1967        let result = expr.search(&json!(null)).unwrap();
1968        // Uniform-like: negative excess kurtosis (platykurtic)
1969        assert!(result.as_f64().unwrap() < 0.0);
1970    }
1971
1972    #[test]
1973    fn test_kurtosis_too_few() {
1974        let runtime = setup_runtime();
1975        let expr = runtime.compile("kurtosis(`[1, 2, 3]`)").unwrap();
1976        let result = expr.search(&json!(null)).unwrap();
1977        assert!(result.is_null());
1978    }
1979
1980    #[test]
1981    fn test_kurtosis_constant() {
1982        let runtime = setup_runtime();
1983        let expr = runtime.compile("kurtosis(`[5, 5, 5, 5]`)").unwrap();
1984        let result = expr.search(&json!(null)).unwrap();
1985        assert_eq!(result.as_f64().unwrap(), 0.0);
1986    }
1987
1988    #[test]
1989    fn test_mad_simple() {
1990        let runtime = setup_runtime();
1991        let expr = runtime.compile("mad(`[1, 2, 3, 4, 5]`)").unwrap();
1992        let result = expr.search(&json!(null)).unwrap();
1993        // Median = 3, deviations = [2, 1, 0, 1, 2], sorted = [0, 1, 1, 2, 2], median = 1.0
1994        assert_eq!(result.as_f64().unwrap(), 1.0);
1995    }
1996
1997    #[test]
1998    fn test_mad_empty() {
1999        let runtime = setup_runtime();
2000        let expr = runtime.compile("mad(`[]`)").unwrap();
2001        let result = expr.search(&json!(null)).unwrap();
2002        assert!(result.is_null());
2003    }
2004
2005    #[test]
2006    fn test_mad_single() {
2007        let runtime = setup_runtime();
2008        let expr = runtime.compile("mad(`[42]`)").unwrap();
2009        let result = expr.search(&json!(null)).unwrap();
2010        // Single value: median = 42, deviation = 0, MAD = 0
2011        assert_eq!(result.as_f64().unwrap(), 0.0);
2012    }
2013
2014    #[test]
2015    fn test_mad_even_length() {
2016        let runtime = setup_runtime();
2017        let expr = runtime.compile("mad(`[1, 2, 3, 4]`)").unwrap();
2018        let result = expr.search(&json!(null)).unwrap();
2019        // Median = 2.5, deviations = [1.5, 0.5, 0.5, 1.5], sorted = [0.5, 0.5, 1.5, 1.5], median = 1.0
2020        assert_eq!(result.as_f64().unwrap(), 1.0);
2021    }
2022}