spb/earth-now Public License
earth-now.co — real-time planetary dashboard: live world metrics modeled, not streamed.
TypeScript 93%
Shell 2.3%
SQL 1.4%
JavaScript 1.3%
Dockerfile 1.2%
CSS 0.8%
1/**2 * earth-now.co3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: packages/models/src/kalman.ts6 * Purpose: 1D Kalman filter (local-level) — observation/forecast fusion: track observations near them, converge to forecast further out7 */89export interface KalmanOptions {10 /** Process noise variance per step (how fast truth can drift). */11 processVariance: number;12 /** Measurement noise variance (how much we trust each observation). */13 measurementVariance: number;14 /** Initial state estimate. */15 initialValue: number;16 /** Initial estimate variance. */17 initialVariance: number;18}1920export interface KalmanStep {21 value: number;22 variance: number;23}2425/** Run a local-level Kalman filter over the series; returns the filtered state at each step. */26export function kalmanFilter(series: readonly number[], opts: KalmanOptions): KalmanStep[] {27 const { processVariance: q, measurementVariance: r } = opts;28 let x = opts.initialValue;29 let p = opts.initialVariance;30 const out: KalmanStep[] = [];31 for (const z of series) {32 // Predict.33 p += q;34 // Update.35 const k = p / (p + r);36 x += k * (z - x);37 p *= 1 - k;38 out.push({ value: x, variance: p });39 }40 return out;41}4243/**44 * Blend the last filtered observation state with an external forecast:45 * inverse-variance weighting where the observation's variance grows with the46 * horizon (q per step), so weight shifts smoothly toward the forecast.47 */48export function blendWithForecast(49 lastState: KalmanStep,50 stepsAhead: number,51 processVariance: number,52 forecast: { value: number; variance: number },53): KalmanStep {54 const obsVariance = lastState.variance + Math.max(0, stepsAhead) * processVariance;55 const wObs = 1 / obsVariance;56 const wFc = 1 / forecast.variance;57 const value = (lastState.value * wObs + forecast.value * wFc) / (wObs + wFc);58 return { value, variance: 1 / (wObs + wFc) };59}60