///|
/// A one-step or multi-step forecast with uncertainty metadata.
pub(all) struct ForecastResult {
predictions : Array[Double]
slope : Double
intercept : Double
residual_sd : Double
lower_95 : Array[Double]
upper_95 : Array[Double]
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Fit a linear forecast and return approximate 95% prediction bounds.
pub fn forecast_linear(values : Array[Double], horizon : Int) -> ForecastResult {
let trend = fit_linear_trend(values)
let predictions = []
let lower = []
let upper = []
let residual = trend.residual_standard_error
let horizon_count = if horizon < 0 { 0 } else { horizon }
for step in 0.. ForecastResult {
let values = []
for measurement in history {
values.push(measurement.rmssd)
}
forecast_linear(values, horizon)
}
///|
/// Calculate a rolling standardized deviation from a local baseline.
pub fn rolling_anomaly_scores(
values : Array[Double],
window_size : Int,
) -> Array[Double] {
let result = []
if window_size <= 0 {
return result
}
for i in 0.. Double {
let n = if actual.length() < forecast.predictions.length() {
actual.length()
} else {
forecast.predictions.length()
}
if n == 0 {
0.0
} else {
let mut covered = 0
for i in 0..= forecast.lower_95[i] && actual[i] <= forecast.upper_95[i] {
covered += 1
}
}
covered.to_double() / n.to_double()
}
}
///|
/// Calculate mean absolute forecast error.
pub fn forecast_mae(
actual : Array[Double],
predicted : Array[Double],
) -> Double {
let n = if actual.length() < predicted.length() {
actual.length()
} else {
predicted.length()
}
if n == 0 {
0.0
} else {
let mut error = 0.0
for i in 0.. Bool {
score.abs() >= (if threshold < 0.0 { 0.0 } else { threshold })
}