Skip to main content

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}