1use 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
12pub 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, "trend", enabled, Box::new(TrendFn::new()));
93 register_if_enabled(
94 runtime,
95 "trend_slope",
96 enabled,
97 Box::new(TrendSlopeFn::new()),
98 );
99 register_if_enabled(
100 runtime,
101 "rate_of_change",
102 enabled,
103 Box::new(RateOfChangeFn::new()),
104 );
105 register_if_enabled(
106 runtime,
107 "cumulative_sum",
108 enabled,
109 Box::new(CumulativeSumFn::new()),
110 );
111}
112
113defn!(RoundFn, vec![arg!(number)], Some(arg!(number)));
118
119impl Function for RoundFn {
120 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
121 self.signature.validate(args, ctx)?;
122
123 let n = args[0].as_f64().unwrap();
124
125 let precision = if args.len() > 1 {
126 args[1].as_f64().map(|p| p as i32).unwrap_or(0)
127 } else {
128 0
129 };
130
131 let result = if precision == 0 {
132 n.round()
133 } else {
134 let multiplier = 10_f64.powi(precision);
135 (n * multiplier).round() / multiplier
136 };
137
138 Ok(number_value(result))
139 }
140}
141
142defn!(FloorFnExt, vec![arg!(number)], None);
147
148impl Function for FloorFnExt {
149 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
150 self.signature.validate(args, ctx)?;
151 let n = args[0].as_f64().unwrap();
152 Ok(Value::Number(Number::from(n.floor() as i64)))
153 }
154}
155
156defn!(CeilFnExt, vec![arg!(number)], None);
161
162impl Function for CeilFnExt {
163 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
164 self.signature.validate(args, ctx)?;
165 let n = args[0].as_f64().unwrap();
166 Ok(Value::Number(Number::from(n.ceil() as i64)))
167 }
168}
169
170defn!(AbsFnExt, vec![arg!(number)], None);
175
176impl Function for AbsFnExt {
177 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
178 self.signature.validate(args, ctx)?;
179 let n = args[0].as_f64().unwrap();
180 Ok(number_value(n.abs()))
181 }
182}
183
184defn!(ModFn, vec![arg!(number), arg!(number)], None);
189
190impl Function for ModFn {
191 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
192 self.signature.validate(args, ctx)?;
193
194 let n = args[0].as_f64().unwrap();
195 let divisor = args[1].as_f64().unwrap();
196
197 if divisor == 0.0 {
198 return Err(crate::functions::custom_error(ctx, "Division by zero"));
199 }
200
201 Ok(number_value(n % divisor))
202 }
203}
204
205defn!(PowFn, vec![arg!(number), arg!(number)], None);
210
211impl Function for PowFn {
212 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
213 self.signature.validate(args, ctx)?;
214 let base = args[0].as_f64().unwrap();
215 let exp = args[1].as_f64().unwrap();
216 Ok(number_value(base.powf(exp)))
217 }
218}
219
220defn!(SqrtFn, vec![arg!(number)], None);
225
226impl Function for SqrtFn {
227 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
228 self.signature.validate(args, ctx)?;
229 let n = args[0].as_f64().unwrap();
230
231 if n < 0.0 {
232 return Err(crate::functions::custom_error(
233 ctx,
234 "Cannot take square root of negative number",
235 ));
236 }
237
238 Ok(number_value(n.sqrt()))
239 }
240}
241
242defn!(LogFn, vec![arg!(number)], Some(arg!(number)));
247
248impl Function for LogFn {
249 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
250 self.signature.validate(args, ctx)?;
251
252 let n = args[0].as_f64().unwrap();
253
254 if n <= 0.0 {
255 return Err(crate::functions::custom_error(
256 ctx,
257 "Logarithm requires positive number",
258 ));
259 }
260
261 let result = if args.len() > 1 {
262 let base = args[1].as_f64().unwrap();
263 n.log(base)
264 } else {
265 n.ln()
266 };
267
268 Ok(number_value(result))
269 }
270}
271
272defn!(
277 ClampFn,
278 vec![arg!(number), arg!(number), arg!(number)],
279 None
280);
281
282impl Function for ClampFn {
283 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
284 self.signature.validate(args, ctx)?;
285
286 let n = args[0].as_f64().unwrap();
287 let min = args[1].as_f64().unwrap();
288 let max = args[2].as_f64().unwrap();
289
290 let result = n.max(min).min(max);
291
292 Ok(number_value(result))
293 }
294}
295
296defn!(MedianFn, vec![arg!(array)], None);
301
302impl Function for MedianFn {
303 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
304 self.signature.validate(args, ctx)?;
305
306 let arr = args[0].as_array().unwrap();
307
308 let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
309
310 if numbers.is_empty() {
311 return Ok(Value::Null);
312 }
313
314 numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
315
316 let len = numbers.len();
317 let median = if len.is_multiple_of(2) {
318 (numbers[len / 2 - 1] + numbers[len / 2]) / 2.0
319 } else {
320 numbers[len / 2]
321 };
322
323 Ok(number_value(median))
324 }
325}
326
327defn!(PercentileFn, vec![arg!(array), arg!(number)], None);
332
333impl Function for PercentileFn {
334 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
335 self.signature.validate(args, ctx)?;
336
337 let arr = args[0].as_array().unwrap();
338 let p = args[1].as_f64().unwrap();
339
340 if !(0.0..=100.0).contains(&p) {
341 return Err(crate::functions::custom_error(
342 ctx,
343 "Percentile must be between 0 and 100",
344 ));
345 }
346
347 let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
348
349 if numbers.is_empty() {
350 return Ok(Value::Null);
351 }
352
353 numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
354
355 let len = numbers.len();
356 if len == 1 {
357 return Ok(number_value(numbers[0]));
358 }
359
360 let rank = (p / 100.0) * (len - 1) as f64;
361 let lower_idx = rank.floor() as usize;
362 let upper_idx = rank.ceil() as usize;
363 let fraction = rank - lower_idx as f64;
364
365 let result = if lower_idx == upper_idx {
366 numbers[lower_idx]
367 } else {
368 numbers[lower_idx] * (1.0 - fraction) + numbers[upper_idx] * fraction
369 };
370
371 Ok(number_value(result))
372 }
373}
374
375defn!(VarianceFn, vec![arg!(array)], None);
380
381impl Function for VarianceFn {
382 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
383 self.signature.validate(args, ctx)?;
384
385 let arr = args[0].as_array().unwrap();
386
387 let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
388
389 if numbers.is_empty() {
390 return Ok(Value::Null);
391 }
392
393 let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
394 let variance =
395 numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
396
397 Ok(number_value(variance))
398 }
399}
400
401defn!(StddevFn, vec![arg!(array)], None);
406
407impl Function for StddevFn {
408 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
409 self.signature.validate(args, ctx)?;
410
411 let arr = args[0].as_array().unwrap();
412
413 let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
414
415 if numbers.is_empty() {
416 return Ok(Value::Null);
417 }
418
419 let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
420 let variance =
421 numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
422 let stddev = variance.sqrt();
423
424 Ok(number_value(stddev))
425 }
426}
427
428defn!(SinFn, vec![arg!(number)], None);
433
434impl Function for SinFn {
435 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
436 self.signature.validate(args, ctx)?;
437 let n = args[0].as_f64().unwrap();
438 Ok(number_value(n.sin()))
439 }
440}
441
442defn!(CosFn, vec![arg!(number)], None);
443
444impl Function for CosFn {
445 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
446 self.signature.validate(args, ctx)?;
447 let n = args[0].as_f64().unwrap();
448 Ok(number_value(n.cos()))
449 }
450}
451
452defn!(TanFn, vec![arg!(number)], None);
453
454impl Function for TanFn {
455 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
456 self.signature.validate(args, ctx)?;
457 let n = args[0].as_f64().unwrap();
458 Ok(number_value(n.tan()))
459 }
460}
461
462defn!(AsinFn, vec![arg!(number)], None);
463
464impl Function for AsinFn {
465 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
466 self.signature.validate(args, ctx)?;
467 let n = args[0].as_f64().unwrap();
468 let result = n.asin();
469 if result.is_nan() {
471 Ok(Value::Null)
472 } else {
473 Ok(number_value(result))
474 }
475 }
476}
477
478defn!(AcosFn, vec![arg!(number)], None);
479
480impl Function for AcosFn {
481 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
482 self.signature.validate(args, ctx)?;
483 let n = args[0].as_f64().unwrap();
484 let result = n.acos();
485 if result.is_nan() {
487 Ok(Value::Null)
488 } else {
489 Ok(number_value(result))
490 }
491 }
492}
493
494defn!(AtanFn, vec![arg!(number)], None);
495
496impl Function for AtanFn {
497 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
498 self.signature.validate(args, ctx)?;
499 let n = args[0].as_f64().unwrap();
500 Ok(number_value(n.atan()))
501 }
502}
503
504defn!(Atan2Fn, vec![arg!(number), arg!(number)], None);
505
506impl Function for Atan2Fn {
507 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
508 self.signature.validate(args, ctx)?;
509 let y = args[0].as_f64().unwrap();
510 let x = args[1].as_f64().unwrap();
511 Ok(number_value(y.atan2(x)))
512 }
513}
514
515defn!(DegToRadFn, vec![arg!(number)], None);
516
517impl Function for DegToRadFn {
518 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
519 self.signature.validate(args, ctx)?;
520 let n = args[0].as_f64().unwrap();
521 Ok(number_value(n.to_radians()))
522 }
523}
524
525defn!(RadToDegFn, vec![arg!(number)], None);
526
527impl Function for RadToDegFn {
528 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
529 self.signature.validate(args, ctx)?;
530 let n = args[0].as_f64().unwrap();
531 Ok(number_value(n.to_degrees()))
532 }
533}
534
535defn!(SignFn, vec![arg!(number)], None);
536
537impl Function for SignFn {
538 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
539 self.signature.validate(args, ctx)?;
540 let n = args[0].as_f64().unwrap();
541 let sign = if n > 0.0 {
542 1
543 } else if n < 0.0 {
544 -1
545 } else {
546 0
547 };
548 Ok(Value::Number(Number::from(sign)))
549 }
550}
551
552defn!(AddFn, vec![arg!(number), arg!(number)], None);
557
558impl Function for AddFn {
559 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
560 self.signature.validate(args, ctx)?;
561 let a = args[0].as_f64().unwrap();
562 let b = args[1].as_f64().unwrap();
563 Ok(number_value(a + b))
564 }
565}
566
567defn!(SubtractFn, vec![arg!(number), arg!(number)], None);
572
573impl Function for SubtractFn {
574 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
575 self.signature.validate(args, ctx)?;
576 let a = args[0].as_f64().unwrap();
577 let b = args[1].as_f64().unwrap();
578 Ok(number_value(a - b))
579 }
580}
581
582defn!(MultiplyFn, vec![arg!(number), arg!(number)], None);
587
588impl Function for MultiplyFn {
589 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
590 self.signature.validate(args, ctx)?;
591 let a = args[0].as_f64().unwrap();
592 let b = args[1].as_f64().unwrap();
593 Ok(number_value(a * b))
594 }
595}
596
597defn!(DivideFn, vec![arg!(number), arg!(number)], None);
602
603impl Function for DivideFn {
604 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
605 self.signature.validate(args, ctx)?;
606 let a = args[0].as_f64().unwrap();
607 let b = args[1].as_f64().unwrap();
608 if b == 0.0 {
609 return Err(crate::functions::custom_error(ctx, "Division by zero"));
610 }
611 Ok(number_value(a / b))
612 }
613}
614
615defn!(ModeFn, vec![arg!(array)], None);
620
621impl Function for ModeFn {
622 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
623 self.signature.validate(args, ctx)?;
624
625 let arr = args[0].as_array().unwrap();
626
627 if arr.is_empty() {
628 return Ok(Value::Null);
629 }
630
631 let mut counts: std::collections::HashMap<String, (usize, Value)> =
633 std::collections::HashMap::new();
634
635 for item in arr.iter() {
636 let key = serde_json::to_string(item).unwrap_or_default();
637 counts
638 .entry(key)
639 .and_modify(|(count, _)| *count += 1)
640 .or_insert((1, item.clone()));
641 }
642
643 let (_, (_, mode_value)) = counts
645 .into_iter()
646 .max_by_key(|(_, (count, _))| *count)
647 .unwrap();
648
649 Ok(mode_value)
650 }
651}
652
653defn!(ToFixedFn, vec![arg!(number), arg!(number)], None);
658
659impl Function for ToFixedFn {
660 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
661 self.signature.validate(args, ctx)?;
662
663 let num = args[0].as_f64().unwrap();
664 let precision = args[1].as_f64().unwrap() as usize;
665
666 let result = format!("{:.prec$}", num, prec = precision);
667 Ok(Value::String(result))
668 }
669}
670
671defn!(FormatNumberFn, vec![arg!(number)], Some(arg!(any)));
676
677impl Function for FormatNumberFn {
678 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
679 self.signature.validate(args, ctx)?;
680
681 let num = args[0].as_f64().unwrap();
682
683 let precision = args
684 .get(1)
685 .and_then(|v| v.as_f64())
686 .map(|n| n as usize)
687 .unwrap_or(0);
688
689 let suffix = args.get(2).and_then(|v| v.as_str()).map(|s| s.to_string());
690
691 let (scaled_num, auto_suffix) = if let Some(ref s) = suffix {
693 match s.as_str() {
694 "k" | "K" => (num / 1_000.0, "k"),
695 "M" => (num / 1_000_000.0, "M"),
696 "B" => (num / 1_000_000_000.0, "B"),
697 "T" => (num / 1_000_000_000_000.0, "T"),
698 "auto" => {
699 let abs_num = num.abs();
700 if abs_num >= 1_000_000_000_000.0 {
701 (num / 1_000_000_000_000.0, "T")
702 } else if abs_num >= 1_000_000_000.0 {
703 (num / 1_000_000_000.0, "B")
704 } else if abs_num >= 1_000_000.0 {
705 (num / 1_000_000.0, "M")
706 } else if abs_num >= 1_000.0 {
707 (num / 1_000.0, "k")
708 } else {
709 (num, "")
710 }
711 }
712 _ => (num, s.as_str()),
713 }
714 } else {
715 (num, "")
716 };
717
718 let formatted = format!("{:.prec$}", scaled_num, prec = precision);
720
721 let result = if suffix.is_none() || suffix.as_deref() == Some("") {
723 add_thousand_separators(&formatted)
724 } else {
725 format!("{}{}", formatted, auto_suffix)
726 };
727
728 Ok(Value::String(result))
729 }
730}
731
732fn add_thousand_separators(s: &str) -> String {
734 let parts: Vec<&str> = s.split('.').collect();
735 let int_part = parts[0];
736 let dec_part = parts.get(1);
737
738 let (sign, digits) = if let Some(stripped) = int_part.strip_prefix('-') {
740 ("-", stripped)
741 } else {
742 ("", int_part)
743 };
744
745 let digit_chars: Vec<char> = digits.chars().collect();
747 let len = digit_chars.len();
748 let with_commas: String = digit_chars
749 .iter()
750 .enumerate()
751 .map(|(i, c)| {
752 let pos_from_right = len - 1 - i;
753 if pos_from_right > 0 && pos_from_right.is_multiple_of(3) {
754 format!("{},", c)
755 } else {
756 c.to_string()
757 }
758 })
759 .collect();
760
761 match dec_part {
762 Some(dec) => format!("{}{}.{}", sign, with_commas, dec),
763 None => format!("{}{}", sign, with_commas),
764 }
765}
766
767defn!(HistogramFn, vec![arg!(array), arg!(number)], None);
772
773impl Function for HistogramFn {
774 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
775 self.signature.validate(args, ctx)?;
776
777 let arr = args[0].as_array().unwrap();
778
779 let num_bins = args[1].as_f64().unwrap() as usize;
780
781 if num_bins == 0 {
782 return Err(crate::functions::custom_error(
783 ctx,
784 "Number of bins must be greater than 0",
785 ));
786 }
787
788 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
790
791 if values.is_empty() {
792 return Ok(Value::Array(vec![]));
793 }
794
795 let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
796 let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
797
798 let bin_width = if (max_val - min_val).abs() < f64::EPSILON {
800 1.0
801 } else {
802 (max_val - min_val) / num_bins as f64
803 };
804
805 let mut bins: Vec<(f64, f64, usize)> = (0..num_bins)
807 .map(|i| {
808 let bin_min = min_val + (i as f64 * bin_width);
809 let bin_max = if i == num_bins - 1 {
810 max_val
811 } else {
812 min_val + ((i + 1) as f64 * bin_width)
813 };
814 (bin_min, bin_max, 0)
815 })
816 .collect();
817
818 for val in &values {
820 let bin_idx = if (max_val - min_val).abs() < f64::EPSILON {
821 0
822 } else {
823 let idx = ((val - min_val) / bin_width) as usize;
824 idx.min(num_bins - 1)
825 };
826 bins[bin_idx].2 += 1;
827 }
828
829 let result: Vec<Value> = bins
831 .into_iter()
832 .map(|(bin_min, bin_max, count)| {
833 let mut map = serde_json::Map::new();
834 map.insert("min".to_string(), number_value(bin_min));
835 map.insert("max".to_string(), number_value(bin_max));
836 map.insert("count".to_string(), Value::Number(Number::from(count)));
837 Value::Object(map)
838 })
839 .collect();
840
841 Ok(Value::Array(result))
842 }
843}
844
845defn!(NormalizeFn, vec![arg!(array)], None);
850
851impl Function for NormalizeFn {
852 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
853 self.signature.validate(args, ctx)?;
854
855 let arr = args[0].as_array().unwrap();
856
857 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
858
859 if values.is_empty() {
860 return Ok(Value::Array(vec![]));
861 }
862
863 let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
864 let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
865 let range = max_val - min_val;
866
867 let result: Vec<Value> = values
868 .iter()
869 .map(|v| {
870 let normalized = if range.abs() < f64::EPSILON {
871 0.0
872 } else {
873 (v - min_val) / range
874 };
875 number_value(normalized)
876 })
877 .collect();
878
879 Ok(Value::Array(result))
880 }
881}
882
883defn!(ZScoreFn, vec![arg!(array)], None);
888
889impl Function for ZScoreFn {
890 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
891 self.signature.validate(args, ctx)?;
892
893 let arr = args[0].as_array().unwrap();
894
895 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
896
897 if values.is_empty() {
898 return Ok(Value::Array(vec![]));
899 }
900
901 let n = values.len() as f64;
902 let mean = values.iter().sum::<f64>() / n;
903 let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
904 let stddev = variance.sqrt();
905
906 let result: Vec<Value> = values
907 .iter()
908 .map(|v| {
909 let z = if stddev.abs() < f64::EPSILON {
910 0.0
911 } else {
912 (v - mean) / stddev
913 };
914 number_value(z)
915 })
916 .collect();
917
918 Ok(Value::Array(result))
919 }
920}
921
922defn!(CorrelationFn, vec![arg!(array), arg!(array)], None);
927
928impl Function for CorrelationFn {
929 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
930 self.signature.validate(args, ctx)?;
931
932 let arr1 = args[0].as_array().unwrap();
933 let arr2 = args[1].as_array().unwrap();
934
935 let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_f64()).collect();
936 let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_f64()).collect();
937
938 if values1.is_empty() || values2.is_empty() {
939 return Ok(Value::Null);
940 }
941
942 let n = values1.len().min(values2.len());
944 if n == 0 {
945 return Ok(Value::Null);
946 }
947
948 let values1 = &values1[..n];
949 let values2 = &values2[..n];
950
951 let mean1 = values1.iter().sum::<f64>() / n as f64;
952 let mean2 = values2.iter().sum::<f64>() / n as f64;
953
954 let mut cov = 0.0;
955 let mut var1 = 0.0;
956 let mut var2 = 0.0;
957
958 for i in 0..n {
959 let d1 = values1[i] - mean1;
960 let d2 = values2[i] - mean2;
961 cov += d1 * d2;
962 var1 += d1 * d1;
963 var2 += d2 * d2;
964 }
965
966 let denom = (var1 * var2).sqrt();
967 let correlation = if denom.abs() < f64::EPSILON {
968 0.0
969 } else {
970 cov / denom
971 };
972
973 Ok(number_value(correlation))
974 }
975}
976
977defn!(QuantileFn, vec![arg!(array), arg!(number)], None);
982
983impl Function for QuantileFn {
984 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
985 self.signature.validate(args, ctx)?;
986
987 let arr = args[0].as_array().unwrap();
988 let q = args[1].as_f64().unwrap();
989
990 if !(0.0..=1.0).contains(&q) {
991 return Ok(Value::Null);
992 }
993
994 let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
995
996 if values.is_empty() {
997 return Ok(Value::Null);
998 }
999
1000 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1001
1002 let n = values.len();
1003 let pos = q * (n - 1) as f64;
1004 let lower = pos.floor() as usize;
1005 let upper = pos.ceil() as usize;
1006 let frac = pos - lower as f64;
1007
1008 let result = if lower == upper {
1009 values[lower]
1010 } else {
1011 values[lower] * (1.0 - frac) + values[upper] * frac
1012 };
1013
1014 Ok(number_value(result))
1015 }
1016}
1017
1018defn!(MovingAvgFn, vec![arg!(array), arg!(number)], None);
1023
1024impl Function for MovingAvgFn {
1025 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1026 self.signature.validate(args, ctx)?;
1027
1028 let arr = args[0].as_array().unwrap();
1029
1030 let window = args[1].as_f64().unwrap() as usize;
1031
1032 if window == 0 {
1033 return Ok(Value::Null);
1034 }
1035
1036 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1037
1038 if values.is_empty() || window > values.len() {
1039 return Ok(Value::Array(vec![]));
1040 }
1041
1042 let mut result: Vec<Value> = Vec::new();
1043
1044 for i in 0..values.len() {
1045 if i + 1 < window {
1046 result.push(Value::Null);
1047 } else {
1048 let start = i + 1 - window;
1049 let sum: f64 = values[start..=i].iter().sum();
1050 let avg = sum / window as f64;
1051 result.push(number_value(avg));
1052 }
1053 }
1054
1055 Ok(Value::Array(result))
1056 }
1057}
1058
1059defn!(EwmaFn, vec![arg!(array), arg!(number)], None);
1064
1065impl Function for EwmaFn {
1066 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1067 self.signature.validate(args, ctx)?;
1068
1069 let arr = args[0].as_array().unwrap();
1070 let alpha = args[1].as_f64().unwrap();
1071
1072 if !(0.0..=1.0).contains(&alpha) {
1073 return Ok(Value::Null);
1074 }
1075
1076 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1077
1078 if values.is_empty() {
1079 return Ok(Value::Array(vec![]));
1080 }
1081
1082 let mut result: Vec<Value> = Vec::new();
1083 let mut ewma = values[0];
1084
1085 for value in &values {
1086 ewma = alpha * value + (1.0 - alpha) * ewma;
1087 result.push(number_value(ewma));
1088 }
1089
1090 Ok(Value::Array(result))
1091 }
1092}
1093
1094defn!(CovarianceFn, vec![arg!(array), arg!(array)], None);
1099
1100impl Function for CovarianceFn {
1101 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1102 self.signature.validate(args, ctx)?;
1103
1104 let arr1 = args[0].as_array().unwrap();
1105 let arr2 = args[1].as_array().unwrap();
1106
1107 let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_f64()).collect();
1108 let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_f64()).collect();
1109
1110 if values1.is_empty() || values1.len() != values2.len() {
1111 return Ok(Value::Null);
1112 }
1113
1114 let n = values1.len() as f64;
1115 let mean1: f64 = values1.iter().sum::<f64>() / n;
1116 let mean2: f64 = values2.iter().sum::<f64>() / n;
1117
1118 let cov: f64 = values1
1119 .iter()
1120 .zip(values2.iter())
1121 .map(|(x, y)| (x - mean1) * (y - mean2))
1122 .sum::<f64>()
1123 / n;
1124
1125 Ok(number_value(cov))
1126 }
1127}
1128
1129defn!(StandardizeFn, vec![arg!(array)], None);
1134
1135impl Function for StandardizeFn {
1136 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1137 self.signature.validate(args, ctx)?;
1138
1139 let arr = args[0].as_array().unwrap();
1140
1141 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1142
1143 if values.is_empty() {
1144 return Ok(Value::Array(vec![]));
1145 }
1146
1147 let n = values.len() as f64;
1148 let mean: f64 = values.iter().sum::<f64>() / n;
1149 let variance: f64 = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n;
1150 let std_dev = variance.sqrt();
1151
1152 let result: Vec<Value> = values
1153 .iter()
1154 .map(|x| {
1155 let standardized = if std_dev.abs() < f64::EPSILON {
1156 0.0
1157 } else {
1158 (x - mean) / std_dev
1159 };
1160 number_value(standardized)
1161 })
1162 .collect();
1163
1164 Ok(Value::Array(result))
1165 }
1166}
1167
1168defn!(QuartilesFn, vec![arg!(array)], None);
1173
1174impl Function for QuartilesFn {
1175 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1176 self.signature.validate(args, ctx)?;
1177
1178 let arr = args[0].as_array().unwrap();
1179
1180 let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1181
1182 if values.is_empty() {
1183 return Ok(Value::Null);
1184 }
1185
1186 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1187
1188 let n = values.len();
1189 let min = values[0];
1190 let max = values[n - 1];
1191
1192 let q1 = percentile_value(&values, 25.0);
1193 let q2 = percentile_value(&values, 50.0);
1194 let q3 = percentile_value(&values, 75.0);
1195 let iqr = q3 - q1;
1196
1197 let mut result = serde_json::Map::new();
1198 result.insert("min".to_string(), number_value(min));
1199 result.insert("q1".to_string(), number_value(q1));
1200 result.insert("q2".to_string(), number_value(q2));
1201 result.insert("q3".to_string(), number_value(q3));
1202 result.insert("max".to_string(), number_value(max));
1203 result.insert("iqr".to_string(), number_value(iqr));
1204
1205 Ok(Value::Object(result))
1206 }
1207}
1208
1209fn percentile_value(sorted_values: &[f64], p: f64) -> f64 {
1210 let n = sorted_values.len();
1211 if n == 1 {
1212 return sorted_values[0];
1213 }
1214
1215 let k = (p / 100.0) * (n - 1) as f64;
1216 let f = k.floor() as usize;
1217 let c = k.ceil() as usize;
1218
1219 if f == c {
1220 sorted_values[f]
1221 } else {
1222 let d = k - f as f64;
1223 sorted_values[f] * (1.0 - d) + sorted_values[c] * d
1224 }
1225}
1226
1227defn!(OutliersIqrFn, vec![arg!(array)], Some(arg!(number)));
1232
1233impl Function for OutliersIqrFn {
1234 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1235 self.signature.validate(args, ctx)?;
1236
1237 let arr = args[0].as_array().unwrap();
1238
1239 let multiplier = if args.len() > 1 {
1240 args[1].as_f64().unwrap_or(1.5)
1241 } else {
1242 1.5
1243 };
1244
1245 let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1246
1247 if values.is_empty() {
1248 return Ok(Value::Array(vec![]));
1249 }
1250
1251 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1252
1253 let q1 = percentile_value(&values, 25.0);
1254 let q3 = percentile_value(&values, 75.0);
1255 let iqr = q3 - q1;
1256
1257 let lower_bound = q1 - multiplier * iqr;
1258 let upper_bound = q3 + multiplier * iqr;
1259
1260 let outliers: Vec<Value> = arr
1261 .iter()
1262 .filter(|v| {
1263 if let Some(n) = v.as_f64() {
1264 n < lower_bound || n > upper_bound
1265 } else {
1266 false
1267 }
1268 })
1269 .cloned()
1270 .collect();
1271
1272 Ok(Value::Array(outliers))
1273 }
1274}
1275
1276defn!(OutliersZscoreFn, vec![arg!(array)], Some(arg!(number)));
1281
1282impl Function for OutliersZscoreFn {
1283 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1284 self.signature.validate(args, ctx)?;
1285
1286 let arr = args[0].as_array().unwrap();
1287
1288 let threshold = if args.len() > 1 {
1289 args[1].as_f64().unwrap_or(2.0)
1290 } else {
1291 2.0
1292 };
1293
1294 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1295
1296 if values.is_empty() {
1297 return Ok(Value::Array(vec![]));
1298 }
1299
1300 let n = values.len() as f64;
1301 let mean: f64 = values.iter().sum::<f64>() / n;
1302 let variance: f64 = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
1303 let stddev = variance.sqrt();
1304
1305 if stddev.abs() < f64::EPSILON {
1306 return Ok(Value::Array(vec![]));
1307 }
1308
1309 let outliers: Vec<Value> = arr
1310 .iter()
1311 .filter(|v| {
1312 if let Some(n) = v.as_f64() {
1313 let z = (n - mean) / stddev;
1314 z.abs() > threshold
1315 } else {
1316 false
1317 }
1318 })
1319 .cloned()
1320 .collect();
1321
1322 Ok(Value::Array(outliers))
1323 }
1324}
1325
1326defn!(TrendFn, vec![arg!(array)], None);
1331
1332impl Function for TrendFn {
1333 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1334 self.signature.validate(args, ctx)?;
1335
1336 let arr = args[0].as_array().unwrap();
1337
1338 if arr.len() < 2 {
1339 return Ok(Value::String("stable".to_string()));
1340 }
1341
1342 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1343
1344 if values.len() < 2 {
1345 return Ok(Value::String("stable".to_string()));
1346 }
1347
1348 let n = values.len() as f64;
1350 let sum_x: f64 = (0..values.len()).map(|i| i as f64).sum();
1351 let sum_y: f64 = values.iter().sum();
1352 let sum_xy: f64 = values.iter().enumerate().map(|(i, y)| i as f64 * y).sum();
1353 let sum_x2: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
1354
1355 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x.powi(2));
1356
1357 let mean: f64 = sum_y / n;
1359 let threshold = mean.abs() * 0.01;
1360
1361 let trend = if slope > threshold {
1362 "increasing"
1363 } else if slope < -threshold {
1364 "decreasing"
1365 } else {
1366 "stable"
1367 };
1368
1369 Ok(Value::String(trend.to_string()))
1370 }
1371}
1372
1373defn!(TrendSlopeFn, vec![arg!(array)], None);
1378
1379impl Function for TrendSlopeFn {
1380 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1381 self.signature.validate(args, ctx)?;
1382
1383 let arr = args[0].as_array().unwrap();
1384
1385 if arr.len() < 2 {
1386 return Ok(number_value(0.0));
1387 }
1388
1389 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1390
1391 if values.len() < 2 {
1392 return Ok(number_value(0.0));
1393 }
1394
1395 let n = values.len() as f64;
1397 let sum_x: f64 = (0..values.len()).map(|i| i as f64).sum();
1398 let sum_y: f64 = values.iter().sum();
1399 let sum_xy: f64 = values.iter().enumerate().map(|(i, y)| i as f64 * y).sum();
1400 let sum_x2: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
1401
1402 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x.powi(2));
1403
1404 Ok(number_value(slope))
1405 }
1406}
1407
1408defn!(RateOfChangeFn, vec![arg!(array)], None);
1413
1414impl Function for RateOfChangeFn {
1415 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1416 self.signature.validate(args, ctx)?;
1417
1418 let arr = args[0].as_array().unwrap();
1419
1420 if arr.len() < 2 {
1421 return Ok(Value::Array(vec![]));
1422 }
1423
1424 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1425
1426 if values.len() < 2 {
1427 return Ok(Value::Array(vec![]));
1428 }
1429
1430 let changes: Vec<Value> = values
1431 .windows(2)
1432 .map(|w| {
1433 let prev = w[0];
1434 let curr = w[1];
1435 let pct_change = if prev != 0.0 {
1436 ((curr - prev) / prev) * 100.0
1437 } else {
1438 0.0
1439 };
1440 number_value(pct_change)
1441 })
1442 .collect();
1443
1444 Ok(Value::Array(changes))
1445 }
1446}
1447
1448defn!(CumulativeSumFn, vec![arg!(array)], None);
1453
1454impl Function for CumulativeSumFn {
1455 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1456 self.signature.validate(args, ctx)?;
1457
1458 let arr = args[0].as_array().unwrap();
1459
1460 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1461
1462 let mut running_sum = 0.0;
1463 let cumsum: Vec<Value> = values
1464 .iter()
1465 .map(|v| {
1466 running_sum += v;
1467 number_value(running_sum)
1468 })
1469 .collect();
1470
1471 Ok(Value::Array(cumsum))
1472 }
1473}