linreg_core/wasm/diagnostics.rs
1//! Diagnostic tests for WASM
2//!
3//! Provides WASM bindings for all statistical diagnostic tests.
4
5#![cfg(feature = "wasm")]
6
7use wasm_bindgen::prelude::*;
8
9use super::domain::check_domain;
10use crate::diagnostics;
11use crate::error::{error_json, error_to_json};
12
13/// Performs the Rainbow test for linearity via WASM.
14///
15/// The Rainbow test checks whether the relationship between predictors and response
16/// is linear. A significant p-value suggests non-linearity.
17///
18/// # Arguments
19///
20/// * `y_json` - JSON array of response variable values
21/// * `x_vars_json` - JSON array of predictor arrays
22/// * `fraction` - Fraction of data to use in the central subset (0.0 to 1.0, typically 0.5)
23/// * `method` - Method to use: "r", "python", or "both" (case-insensitive, defaults to "r")
24///
25/// # Returns
26///
27/// JSON string containing test statistic, p-value, and interpretation.
28///
29/// # Errors
30///
31/// Returns a JSON error object if parsing fails or domain check fails.
32#[wasm_bindgen]
33pub fn rainbow_test(y_json: &str, x_vars_json: &str, fraction: f64, method: &str) -> String {
34 if let Err(e) = check_domain() {
35 return error_to_json(&e);
36 }
37
38 let y: Vec<f64> = match serde_json::from_str(y_json) {
39 Ok(v) => v,
40 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
41 };
42
43 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
44 Ok(v) => v,
45 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
46 };
47
48 // Parse method parameter (default to "r" for R)
49 let method = match method.to_lowercase().as_str() {
50 "python" => diagnostics::RainbowMethod::Python,
51 "both" => diagnostics::RainbowMethod::Both,
52 _ => diagnostics::RainbowMethod::R, // Default to R
53 };
54
55 match diagnostics::rainbow_test(&y, &x_vars, fraction, method) {
56 Ok(output) => serde_json::to_string(&output)
57 .unwrap_or_else(|_| error_json("Failed to serialize Rainbow test result")),
58 Err(e) => error_json(&e.to_string()),
59 }
60}
61
62/// Performs the Harvey-Collier test for linearity via WASM.
63///
64/// The Harvey-Collier test checks whether the residuals exhibit a linear trend,
65/// which would indicate that the model's functional form is misspecified.
66/// A significant p-value suggests non-linearity.
67///
68/// # Arguments
69///
70/// * `y_json` - JSON array of response variable values
71/// * `x_vars_json` - JSON array of predictor arrays
72///
73/// # Returns
74///
75/// JSON string containing test statistic, p-value, and interpretation.
76///
77/// # Errors
78///
79/// Returns a JSON error object if parsing fails or domain check fails.
80#[wasm_bindgen]
81pub fn harvey_collier_test(y_json: &str, x_vars_json: &str) -> String {
82 if let Err(e) = check_domain() {
83 return error_to_json(&e);
84 }
85
86 let y: Vec<f64> = match serde_json::from_str(y_json) {
87 Ok(v) => v,
88 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
89 };
90
91 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
92 Ok(v) => v,
93 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
94 };
95
96 match diagnostics::harvey_collier_test(&y, &x_vars, diagnostics::HarveyCollierMethod::R) {
97 Ok(output) => serde_json::to_string(&output)
98 .unwrap_or_else(|_| error_json("Failed to serialize Harvey-Collier test result")),
99 Err(e) => error_json(&e.to_string()),
100 }
101}
102
103/// Performs the Breusch-Pagan test for heteroscedasticity via WASM.
104///
105/// The Breusch-Pagan test checks whether the variance of residuals is constant
106/// across the range of predicted values (homoscedasticity assumption).
107/// A significant p-value suggests heteroscedasticity.
108///
109/// # Arguments
110///
111/// * `y_json` - JSON array of response variable values
112/// * `x_vars_json` - JSON array of predictor arrays
113///
114/// # Returns
115///
116/// JSON string containing test statistic, p-value, and interpretation.
117///
118/// # Errors
119///
120/// Returns a JSON error object if parsing fails or domain check fails.
121#[wasm_bindgen]
122pub fn breusch_pagan_test(y_json: &str, x_vars_json: &str) -> String {
123 if let Err(e) = check_domain() {
124 return error_to_json(&e);
125 }
126
127 let y: Vec<f64> = match serde_json::from_str(y_json) {
128 Ok(v) => v,
129 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
130 };
131
132 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
133 Ok(v) => v,
134 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
135 };
136
137 match diagnostics::breusch_pagan_test(&y, &x_vars) {
138 Ok(output) => serde_json::to_string(&output)
139 .unwrap_or_else(|_| error_json("Failed to serialize Breusch-Pagan test result")),
140 Err(e) => error_json(&e.to_string()),
141 }
142}
143
144/// Performs the White test for heteroscedasticity via WASM.
145///
146/// The White test is a more general test for heteroscedasticity that does not
147/// assume a specific form of heteroscedasticity. A significant p-value suggests
148/// that the error variance is not constant.
149///
150/// # Arguments
151///
152/// * `y_json` - JSON array of response variable values
153/// * `x_vars_json` - JSON array of predictor arrays
154/// * `method` - Method to use: "r", "python", or "both" (case-insensitive, defaults to "r")
155///
156/// # Returns
157///
158/// JSON string containing test statistic, p-value, and interpretation.
159///
160/// # Errors
161///
162/// Returns a JSON error object if parsing fails or domain check fails.
163#[wasm_bindgen]
164pub fn white_test(y_json: &str, x_vars_json: &str, method: &str) -> String {
165 if let Err(e) = check_domain() {
166 return error_to_json(&e);
167 }
168
169 let y: Vec<f64> = match serde_json::from_str(y_json) {
170 Ok(v) => v,
171 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
172 };
173
174 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
175 Ok(v) => v,
176 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
177 };
178
179 // Parse method parameter (default to "r" for R)
180 let method = match method.to_lowercase().as_str() {
181 "python" => diagnostics::WhiteMethod::Python,
182 "both" => diagnostics::WhiteMethod::Both,
183 _ => diagnostics::WhiteMethod::R, // Default to R
184 };
185
186 match diagnostics::white_test(&y, &x_vars, method) {
187 Ok(output) => serde_json::to_string(&output)
188 .unwrap_or_else(|_| error_json("Failed to serialize White test result")),
189 Err(e) => error_json(&e.to_string()),
190 }
191}
192
193/// Performs the R method White test for heteroscedasticity via WASM.
194///
195/// This implementation matches R's `skedastic::white()` function behavior.
196/// Uses the standard QR decomposition and the R-specific auxiliary matrix
197/// structure (intercept, X, X² only - no cross-products).
198///
199/// # Arguments
200///
201/// * `y_json` - JSON array of response variable values
202/// * `x_vars_json` - JSON array of predictor arrays (each array is a column)
203///
204/// # Returns
205///
206/// JSON string containing test statistic, p-value, and interpretation.
207///
208/// # Errors
209///
210/// Returns a JSON error object if parsing fails or domain check fails.
211#[wasm_bindgen]
212pub fn r_white_test(y_json: &str, x_vars_json: &str) -> String {
213 if let Err(e) = check_domain() {
214 return error_to_json(&e);
215 }
216
217 let y: Vec<f64> = match serde_json::from_str(y_json) {
218 Ok(v) => v,
219 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
220 };
221
222 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
223 Ok(v) => v,
224 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
225 };
226
227 match diagnostics::r_white_method(&y, &x_vars) {
228 Ok(output) => serde_json::to_string(&output)
229 .unwrap_or_else(|_| error_json("Failed to serialize R White test result")),
230 Err(e) => error_json(&e.to_string()),
231 }
232}
233
234/// Performs the Python method White test for heteroscedasticity via WASM.
235///
236/// This implementation matches Python's `statsmodels.stats.diagnostic.het_white()` function.
237/// Uses the LINPACK QR decomposition with column pivoting and the Python-specific
238/// auxiliary matrix structure (intercept, X, X², and cross-products).
239///
240/// # Arguments
241///
242/// * `y_json` - JSON array of response variable values
243/// * `x_vars_json` - JSON array of predictor arrays (each array is a column)
244///
245/// # Returns
246///
247/// JSON string containing test statistic, p-value, and interpretation.
248///
249/// # Errors
250///
251/// Returns a JSON error object if parsing fails or domain check fails.
252#[wasm_bindgen]
253pub fn python_white_test(y_json: &str, x_vars_json: &str) -> String {
254 if let Err(e) = check_domain() {
255 return error_to_json(&e);
256 }
257
258 let y: Vec<f64> = match serde_json::from_str(y_json) {
259 Ok(v) => v,
260 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
261 };
262
263 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
264 Ok(v) => v,
265 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
266 };
267
268 match diagnostics::python_white_method(&y, &x_vars) {
269 Ok(output) => serde_json::to_string(&output)
270 .unwrap_or_else(|_| error_json("Failed to serialize Python White test result")),
271 Err(e) => error_json(&e.to_string()),
272 }
273}
274
275/// Performs the Jarque-Bera test for normality via WASM.
276///
277/// The Jarque-Bera test checks whether the residuals are normally distributed
278/// by examining skewness and kurtosis. A significant p-value suggests that
279/// the residuals deviate from normality.
280///
281/// # Arguments
282///
283/// * `y_json` - JSON array of response variable values
284/// * `x_vars_json` - JSON array of predictor arrays
285///
286/// # Returns
287///
288/// JSON string containing test statistic, p-value, and interpretation.
289///
290/// # Errors
291///
292/// Returns a JSON error object if parsing fails or domain check fails.
293#[wasm_bindgen]
294pub fn jarque_bera_test(y_json: &str, x_vars_json: &str) -> String {
295 if let Err(e) = check_domain() {
296 return error_to_json(&e);
297 }
298
299 let y: Vec<f64> = match serde_json::from_str(y_json) {
300 Ok(v) => v,
301 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
302 };
303
304 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
305 Ok(v) => v,
306 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
307 };
308
309 match diagnostics::jarque_bera_test(&y, &x_vars) {
310 Ok(output) => serde_json::to_string(&output)
311 .unwrap_or_else(|_| error_json("Failed to serialize Jarque-Bera test result")),
312 Err(e) => error_json(&e.to_string()),
313 }
314}
315
316/// Performs the Durbin-Watson test for autocorrelation via WASM.
317///
318/// The Durbin-Watson test checks for autocorrelation in the residuals.
319/// Values near 2 indicate no autocorrelation, values near 0 suggest positive
320/// autocorrelation, and values near 4 suggest negative autocorrelation.
321///
322/// # Arguments
323///
324/// * `y_json` - JSON array of response variable values
325/// * `x_vars_json` - JSON array of predictor arrays
326///
327/// # Returns
328///
329/// JSON string containing the DW statistic and interpretation.
330///
331/// # Errors
332///
333/// Returns a JSON error object if parsing fails or domain check fails.
334#[wasm_bindgen]
335pub fn durbin_watson_test(y_json: &str, x_vars_json: &str) -> String {
336 if let Err(e) = check_domain() {
337 return error_to_json(&e);
338 }
339
340 let y: Vec<f64> = match serde_json::from_str(y_json) {
341 Ok(v) => v,
342 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
343 };
344
345 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
346 Ok(v) => v,
347 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
348 };
349
350 match diagnostics::durbin_watson_test(&y, &x_vars) {
351 Ok(output) => serde_json::to_string(&output)
352 .unwrap_or_else(|_| error_json("Failed to serialize Durbin-Watson test result")),
353 Err(e) => error_json(&e.to_string()),
354 }
355}
356
357/// Performs the Shapiro-Wilk test for normality via WASM.
358///
359/// The Shapiro-Wilk test is a powerful test for normality,
360/// especially for small to moderate sample sizes (3 ≤ n ≤ 5000). It tests
361/// the null hypothesis that the residuals are normally distributed.
362///
363/// # Arguments
364///
365/// * `y_json` - JSON array of response variable values
366/// * `x_vars_json` - JSON array of predictor arrays
367///
368/// # Returns
369///
370/// JSON string containing the W statistic (ranges from 0 to 1), p-value,
371/// and interpretation.
372///
373/// # Errors
374///
375/// Returns a JSON error object if parsing fails or domain check fails.
376#[wasm_bindgen]
377pub fn shapiro_wilk_test(y_json: &str, x_vars_json: &str) -> String {
378 if let Err(e) = check_domain() {
379 return error_to_json(&e);
380 }
381
382 let y: Vec<f64> = match serde_json::from_str(y_json) {
383 Ok(v) => v,
384 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
385 };
386
387 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
388 Ok(v) => v,
389 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
390 };
391
392 match diagnostics::shapiro_wilk_test(&y, &x_vars) {
393 Ok(output) => serde_json::to_string(&output)
394 .unwrap_or_else(|_| error_json("Failed to serialize Shapiro-Wilk test result")),
395 Err(e) => error_json(&e.to_string()),
396 }
397}
398
399/// Performs the Anderson-Darling test for normality via WASM.
400///
401/// The Anderson-Darling test checks whether the residuals are normally distributed
402/// by comparing the empirical distribution to the expected normal distribution.
403/// This test is particularly sensitive to deviations in the tails of the distribution.
404/// A significant p-value suggests that the residuals deviate from normality.
405///
406/// # Arguments
407///
408/// * `y_json` - JSON array of response variable values
409/// * `x_vars_json` - JSON array of predictor arrays
410///
411/// # Returns
412///
413/// JSON string containing the A² statistic, p-value, and interpretation.
414///
415/// # Errors
416///
417/// Returns a JSON error object if parsing fails or domain check fails.
418#[wasm_bindgen]
419pub fn anderson_darling_test(y_json: &str, x_vars_json: &str) -> String {
420 if let Err(e) = check_domain() {
421 return error_to_json(&e);
422 }
423
424 let y: Vec<f64> = match serde_json::from_str(y_json) {
425 Ok(v) => v,
426 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
427 };
428
429 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
430 Ok(v) => v,
431 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
432 };
433
434 match diagnostics::anderson_darling_test(&y, &x_vars) {
435 Ok(output) => serde_json::to_string(&output)
436 .unwrap_or_else(|_| error_json("Failed to serialize Anderson-Darling test result")),
437 Err(e) => error_json(&e.to_string()),
438 }
439}
440
441/// Computes Cook's distance for identifying influential observations via WASM.
442///
443/// Cook's distance measures how much each observation influences the regression
444/// model by comparing coefficient estimates with and without that observation.
445/// Unlike hypothesis tests, this is an influence measure - not a test with p-values.
446///
447/// # Arguments
448///
449/// * `y_json` - JSON array of response variable values
450/// * `x_vars_json` - JSON array of predictor arrays
451///
452/// # Returns
453///
454/// JSON string containing:
455/// - Vector of Cook's distances (one per observation)
456/// - Thresholds for identifying influential observations
457/// - Indices of potentially influential observations
458/// - Interpretation and guidance
459///
460/// # Errors
461///
462/// Returns a JSON error object if parsing fails or domain check fails.
463#[wasm_bindgen]
464pub fn cooks_distance_test(y_json: &str, x_vars_json: &str) -> String {
465 if let Err(e) = check_domain() {
466 return error_to_json(&e);
467 }
468
469 let y: Vec<f64> = match serde_json::from_str(y_json) {
470 Ok(v) => v,
471 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
472 };
473
474 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
475 Ok(v) => v,
476 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
477 };
478
479 match diagnostics::cooks_distance_test(&y, &x_vars) {
480 Ok(output) => serde_json::to_string(&output)
481 .unwrap_or_else(|_| error_json("Failed to serialize Cook's distance result")),
482 Err(e) => error_json(&e.to_string()),
483 }
484}
485
486/// Performs DFBETAS analysis via WASM.
487///
488/// DFBETAS measures the influence of each observation on each regression coefficient.
489/// For each observation and each coefficient, it computes the standardized change
490/// in the coefficient when that observation is omitted.
491///
492/// # Arguments
493///
494/// * `y_json` - JSON array of response variable values
495/// * `x_vars_json` - JSON array of predictor arrays
496///
497/// # Returns
498///
499/// JSON string containing the DFBETAS matrix, threshold, and influential observations.
500///
501/// # Errors
502///
503/// Returns a JSON error object if parsing fails or domain check fails.
504#[wasm_bindgen]
505pub fn dfbetas_test(y_json: &str, x_vars_json: &str) -> String {
506 if let Err(e) = check_domain() {
507 return error_to_json(&e);
508 }
509
510 let y: Vec<f64> = match serde_json::from_str(y_json) {
511 Ok(v) => v,
512 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
513 };
514
515 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
516 Ok(v) => v,
517 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
518 };
519
520 match diagnostics::dfbetas_test(&y, &x_vars) {
521 Ok(output) => serde_json::to_string(&output)
522 .unwrap_or_else(|_| error_json("Failed to serialize DFBETAS result")),
523 Err(e) => error_json(&e.to_string()),
524 }
525}
526
527/// Performs DFFITS analysis via WASM.
528///
529/// DFFITS measures the influence of each observation on its own fitted value.
530/// It is the standardized change in the fitted value when that observation
531/// is omitted from the model.
532///
533/// # Arguments
534///
535/// * `y_json` - JSON array of response variable values
536/// * `x_vars_json` - JSON array of predictor arrays
537///
538/// # Returns
539///
540/// JSON string containing the DFFITS vector, threshold, and influential observations.
541///
542/// # Errors
543///
544/// Returns a JSON error object if parsing fails or domain check fails.
545#[wasm_bindgen]
546pub fn dffits_test(y_json: &str, x_vars_json: &str) -> String {
547 if let Err(e) = check_domain() {
548 return error_to_json(&e);
549 }
550
551 let y: Vec<f64> = match serde_json::from_str(y_json) {
552 Ok(v) => v,
553 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
554 };
555
556 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
557 Ok(v) => v,
558 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
559 };
560
561 match diagnostics::dffits_test(&y, &x_vars) {
562 Ok(output) => serde_json::to_string(&output)
563 .unwrap_or_else(|_| error_json("Failed to serialize DFFITS result")),
564 Err(e) => error_json(&e.to_string()),
565 }
566}
567
568/// Performs Variance Inflation Factor (VIF) analysis via WASM.
569///
570/// VIF measures how much the variance of regression coefficients is inflated
571/// due to multicollinearity among predictor variables. High VIF values indicate
572/// that a predictor is highly correlated with other predictors.
573///
574/// # Arguments
575///
576/// * `y_json` - JSON array of response variable values
577/// * `x_vars_json` - JSON array of predictor arrays
578///
579/// # Returns
580///
581/// JSON string containing the maximum VIF, detailed VIF results for each predictor,
582/// interpretation, and guidance.
583///
584/// # Interpretation
585///
586/// - VIF = 1: No correlation with other predictors
587/// - VIF > 5: Moderate multicollinearity (concerning)
588/// - VIF > 10: High multicollinearity (severe)
589///
590/// # Errors
591///
592/// Returns a JSON error object if parsing fails or domain check fails.
593#[wasm_bindgen]
594pub fn vif_test(y_json: &str, x_vars_json: &str) -> String {
595 if let Err(e) = check_domain() {
596 return error_to_json(&e);
597 }
598
599 let y: Vec<f64> = match serde_json::from_str(y_json) {
600 Ok(v) => v,
601 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
602 };
603
604 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
605 Ok(v) => v,
606 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
607 };
608
609 match diagnostics::vif_test(&y, &x_vars) {
610 Ok(output) => serde_json::to_string(&output)
611 .unwrap_or_else(|_| error_json("Failed to serialize VIF result")),
612 Err(e) => error_json(&e.to_string()),
613 }
614}
615
616/// Performs the RESET test for model specification error via WASM.
617///
618/// The RESET (Regression Specification Error Test) test checks whether the model
619/// is correctly specified by testing if additional terms (powers of fitted values,
620/// regressors, or first principal component) significantly improve the model fit.
621///
622/// # Arguments
623///
624/// * `y_json` - JSON array of response variable values
625/// * `x_vars_json` - JSON array of predictor arrays
626/// * `powers_json` - JSON array of powers to use (e.g., [2, 3] for ŷ², ŷ³)
627/// * `type_` - Type of terms to add: "fitted", "regressor", or "princomp"
628///
629/// # Returns
630///
631/// JSON string containing the F-statistic, p-value, and interpretation.
632///
633/// # Errors
634///
635/// Returns a JSON error object if parsing fails or domain check fails.
636#[wasm_bindgen]
637pub fn reset_test(y_json: &str, x_vars_json: &str, powers_json: &str, type_: &str) -> String {
638 if let Err(e) = check_domain() {
639 return error_to_json(&e);
640 }
641
642 let y: Vec<f64> = match serde_json::from_str(y_json) {
643 Ok(v) => v,
644 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
645 };
646
647 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
648 Ok(v) => v,
649 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
650 };
651
652 let powers: Vec<usize> = match serde_json::from_str(powers_json) {
653 Ok(v) => v,
654 Err(e) => return error_json(&format!("Failed to parse powers: {}", e)),
655 };
656
657 // Parse reset type (default to "fitted")
658 let reset_type = match type_.to_lowercase().as_str() {
659 "regressor" => diagnostics::ResetType::Regressor,
660 "princomp" => diagnostics::ResetType::PrincipalComponent,
661 _ => diagnostics::ResetType::Fitted,
662 };
663
664 match diagnostics::reset_test(&y, &x_vars, &powers, reset_type) {
665 Ok(output) => serde_json::to_string(&output)
666 .unwrap_or_else(|_| error_json("Failed to serialize RESET test result")),
667 Err(e) => error_json(&e.to_string()),
668 }
669}
670
671/// Performs the Breusch-Godfrey test for higher-order serial correlation via WASM.
672///
673/// Unlike the Durbin-Watson test which only detects first-order autocorrelation,
674/// the Breusch-Godfrey test can detect serial correlation at any lag order.
675///
676/// # Arguments
677///
678/// * `y_json` - JSON array of response variable values
679/// * `x_vars_json` - JSON array of predictor arrays
680/// * `order` - Maximum order of serial correlation to test (default: 1)
681/// * `test_type` - Type of test statistic: "chisq" or "f" (default: "chisq")
682///
683/// # Returns
684///
685/// JSON string containing test statistic, p-value, degrees of freedom, and interpretation.
686///
687/// # Errors
688///
689/// Returns a JSON error object if parsing fails or domain check fails.
690#[wasm_bindgen]
691pub fn breusch_godfrey_test(y_json: &str, x_vars_json: &str, order: usize, test_type: &str) -> String {
692 if let Err(e) = check_domain() {
693 return error_to_json(&e);
694 }
695
696 let y: Vec<f64> = match serde_json::from_str(y_json) {
697 Ok(v) => v,
698 Err(e) => return error_json(&format!("Failed to parse y: {}", e)),
699 };
700
701 let x_vars: Vec<Vec<f64>> = match serde_json::from_str(x_vars_json) {
702 Ok(v) => v,
703 Err(e) => return error_json(&format!("Failed to parse x_vars: {}", e)),
704 };
705
706 // Parse test type (default to "chisq")
707 let bg_test_type = match test_type.to_lowercase().as_str() {
708 "f" => diagnostics::BGTestType::F,
709 _ => diagnostics::BGTestType::Chisq,
710 };
711
712 match diagnostics::breusch_godfrey_test(&y, &x_vars, order, bg_test_type) {
713 Ok(output) => serde_json::to_string(&output)
714 .unwrap_or_else(|_| error_json("Failed to serialize Breusch-Godfrey test result")),
715 Err(e) => error_json(&e.to_string()),
716 }
717}