import { isValidObservationForBaseline, resolveBaselineMaturity, } from './health-baseline-maturity.mjs'; const WINDOWS = Object.freeze([7, 30, 90]); function withinWindow(observedAt, windowDays, now = Date.now()) { const ts = Number(observedAt); if (!Number.isFinite(ts)) return false; const start = now - windowDays * 24 * 60 * 60 * 1000; return ts >= start && ts <= now; } function mean(values) { if (!values.length) return null; return values.reduce((sum, v) => sum + v, 0) / values.length; } function stddev(values, avg) { if (values.length < 2) return 0; const m = avg ?? mean(values); const variance = values.reduce((sum, v) => sum + (v - m) ** 2, 0) / values.length; return Math.sqrt(variance); } export function computeMetricBaseline( observations = [], { metricType, context = 'any', windowDays = 30, now = Date.now(), } = {}, ) { const rows = observations.filter((row) => { if (!isValidObservationForBaseline(row)) return false; if (row.metricType !== metricType) return false; if (row.valueNum == null) return false; if (context !== 'any' && row.context !== context) return false; return withinWindow(row.observedAt, windowDays, now); }); const values = rows.map((row) => Number(row.valueNum)).filter(Number.isFinite); const sampleCount = values.length; const avg = mean(values); return { metricType, context, windowDays, sampleCount, maturity: resolveBaselineMaturity(sampleCount), mean: avg == null ? null : Number(avg.toFixed(2)), stddev: avg == null ? null : Number(stddev(values, avg).toFixed(2)), min: values.length ? Math.min(...values) : null, max: values.length ? Math.max(...values) : null, }; } export function computeHealthBaselines(observations = [], { now = Date.now() } = {}) { const metricContexts = [ { metricType: 'bp_systolic', context: 'morning' }, { metricType: 'bp_diastolic', context: 'morning' }, { metricType: 'bp_systolic', context: 'evening' }, { metricType: 'bp_diastolic', context: 'evening' }, { metricType: 'hr', context: 'any' }, { metricType: 'weight', context: 'any' }, { metricType: 'spo2', context: 'any' }, { metricType: 'sleep_minutes', context: 'any' }, ]; const baselines = []; for (const windowDays of WINDOWS) { for (const spec of metricContexts) { baselines.push(computeMetricBaseline(observations, { ...spec, windowDays, now })); } } return baselines; } export function deviationFromBaseline(value, baselineMean) { const num = Number(value); const mean = Number(baselineMean); if (!Number.isFinite(num) || !Number.isFinite(mean) || mean === 0) return null; return Number((((num - mean) / mean) * 100).toFixed(1)); }