use crate::nightscout::Entry;
const BG_REF: f64 = 140.0;
const AR: [f64; 2] = [-0.723, 1.716];
const STEP_MS: i64 = 5 * 60_000;
const STEPS: usize = 6;
const BG_MIN: f64 = 36.0;
const BG_MAX: f64 = 400.0;
pub fn ar2(entries: &[Entry]) -> Vec<(i64, f64)> {
let (latest, prev) = match (entries.first(), entries.get(1)) {
(Some(a), Some(b)) => (a, b),
_ => return Vec::new(),
};
let mut y0 = (prev.sgv / BG_REF).ln();
let mut y1 = (latest.sgv / BG_REF).ln();
let mut out = Vec::with_capacity(STEPS);
for i in 1..=STEPS as i64 {
let y_next = AR[0] * y0 + AR[1] * y1;
y0 = y1;
y1 = y_next;
let bg = (BG_REF * y_next.exp()).clamp(BG_MIN, BG_MAX);
out.push((latest.date + i * STEP_MS, bg));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(sgv: f64, date: i64) -> Entry {
Entry {
sgv,
date,
direction: None,
}
}
#[test]
fn empty_without_two_readings() {
assert!(ar2(&[]).is_empty());
assert!(ar2(&[entry(100.0, 0)]).is_empty());
}
#[test]
fn projects_six_future_points_five_min_apart() {
let now = 1_000_000_000_000;
let out = ar2(&[entry(120.0, now), entry(115.0, now - STEP_MS)]);
assert_eq!(out.len(), STEPS);
assert_eq!(out[0].0, now + STEP_MS);
assert_eq!(out[5].0, now + 6 * STEP_MS);
let flat = ar2(&[entry(100.0, now), entry(100.0, now - STEP_MS)]);
assert!((flat[0].1 - 100.0).abs() < 1.0);
}
#[test]
fn stays_within_physiological_clamp() {
let now = 0;
let out = ar2(&[entry(390.0, now), entry(300.0, now - STEP_MS)]);
assert!(out.iter().all(|&(_, bg)| (BG_MIN..=BG_MAX).contains(&bg)));
}
}