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/counter/test/value.test.ts6 * Purpose: Golden tests for counterValue/rateAt across all RateFunction kinds at precise instants t7 */89import { describe, expect, it } from "vitest";10import {11 type CounterModel,12 counterValue,13 rateAt,14 startOfUtcDay,15 startOfUtcYear,16 windowValue,17 YEAR_SECONDS,18 SEASONAL_EPOCH_MS,19} from "../src/index";2021const T0 = "2026-01-01T00:00:00.000Z";22const t0 = Date.parse(T0);2324function base(over: Partial<CounterModel>): CounterModel {25 return {26 metricId: "test_metric",27 anchorValue: 0,28 anchorTime: T0,29 rateFn: { kind: "linear", perSecond: 1 },30 observedAt: T0,31 sourceId: "test_source",32 modelVersion: "test-v1",33 displayHints: { decimals: 0, unit: "units" },34 ...over,35 };36}3738describe("linear", () => {39 it("value(anchorTime) === anchorValue", () => {40 const m = base({ anchorValue: 42 });41 expect(counterValue(m, t0)).toBe(42);42 });4344 it("advances by perSecond", () => {45 const m = base({ rateFn: { kind: "linear", perSecond: 2.5 } });46 expect(counterValue(m, t0 + 10_000)).toBeCloseTo(25, 10);47 expect(counterValue(m, t0 - 4_000)).toBeCloseTo(-10, 10);48 expect(rateAt(m, t0)).toBe(2.5);49 });50});5152describe("piecewise", () => {53 const m = base({54 anchorValue: 100,55 rateFn: {56 kind: "piecewise",57 segments: [58 { from: "2026-01-01T00:00:00.000Z", perSecond: 1 },59 { from: "2026-01-01T00:01:00.000Z", perSecond: 3 },60 ],61 },62 });6364 it("integrates across the junction", () => {65 // 60 s at 1/s + 30 s at 3/s = 60 + 90.66 expect(counterValue(m, t0 + 90_000)).toBeCloseTo(250, 10);67 });6869 it("is continuous at the junction", () => {70 const eps = 1;71 const before = counterValue(m, t0 + 60_000 - eps);72 const after = counterValue(m, t0 + 60_000 + eps);73 expect(Math.abs(after - before)).toBeLessThan(0.01);74 });7576 it("reports the active segment's rate", () => {77 expect(rateAt(m, t0 + 30_000)).toBe(1);78 expect(rateAt(m, t0 + 61_000)).toBe(3);79 });80});8182describe("seasonal", () => {83 const m = base({84 anchorValue: 1000,85 rateFn: {86 kind: "seasonal",87 base: 2,88 harmonics: [{ period: "year", order: 1, amplitude: 1, phase: 0 }],89 },90 });9192 it("value(anchorTime) === anchorValue", () => {93 expect(counterValue(m, t0)).toBeCloseTo(1000, 9);94 });9596 it("harmonic integrates to ~zero over a full year (only base remains)", () => {97 const oneYearMs = YEAR_SECONDS * 1000;98 const v = counterValue(m, t0 + oneYearMs);99 expect(v).toBeCloseTo(1000 + 2 * YEAR_SECONDS, 4);100 });101102 it("rate oscillates around base with the harmonic amplitude", () => {103 // At the seasonal epoch itself, cos(0 + 0) = 1 → rate = base + amplitude.104 expect(rateAt(m, SEASONAL_EPOCH_MS)).toBeCloseTo(3, 9);105 });106});107108describe("seasonal week period", () => {109 const m = base({110 rateFn: {111 kind: "seasonal",112 base: 10,113 harmonics: [{ period: "week", order: 1, amplitude: 2, phase: 0 }],114 },115 });116117 it("weekly harmonic integrates to ~zero over a full week", () => {118 const oneWeekMs = 604_800_000;119 expect(counterValue(m, t0 + oneWeekMs)).toBeCloseTo(10 * 604_800, 4);120 });121122 it("weekly phase is anchored to Saturday 00:00 UTC (the 2000-01-01 epoch)", () => {123 // 2026-01-03 is a Saturday: cos(0) = 1 → rate = base + amplitude.124 expect(rateAt(m, Date.parse("2026-01-03T00:00:00Z"))).toBeCloseTo(12, 9);125 // Half a week later (Tue 12:00): cos(π) = −1 → base − amplitude.126 expect(rateAt(m, Date.parse("2026-01-06T12:00:00Z"))).toBeCloseTo(8, 9);127 });128});129130describe("spline (PCHIP)", () => {131 const m = base({132 anchorValue: 20,133 anchorTime: "2026-01-02T00:00:00.000Z",134 rateFn: {135 kind: "spline",136 knots: [137 ["2026-01-01T00:00:00.000Z", 10],138 ["2026-01-02T00:00:00.000Z", 20],139 ["2026-01-03T00:00:00.000Z", 40],140 ["2026-01-04T00:00:00.000Z", 45],141 ],142 },143 });144145 it("interpolates exactly at knots and anchor", () => {146 expect(counterValue(m, Date.parse("2026-01-01T00:00:00Z"))).toBeCloseTo(10, 9);147 expect(counterValue(m, Date.parse("2026-01-02T00:00:00Z"))).toBeCloseTo(20, 9);148 expect(counterValue(m, Date.parse("2026-01-04T00:00:00Z"))).toBeCloseTo(45, 9);149 });150151 it("never overshoots between monotone knots", () => {152 const a = Date.parse("2026-01-01T00:00:00Z");153 const b = Date.parse("2026-01-04T00:00:00Z");154 for (let i = 0; i <= 200; i++) {155 const v = counterValue(m, a + ((b - a) * i) / 200);156 expect(v).toBeGreaterThanOrEqual(10 - 1e-9);157 expect(v).toBeLessThanOrEqual(45 + 1e-9);158 }159 });160161 it("extrapolates linearly beyond the last knot", () => {162 const end = Date.parse("2026-01-04T00:00:00Z");163 const slopeEnd = rateAt(m, end);164 const v = counterValue(m, end + 3_600_000);165 expect(v).toBeCloseTo(45 + slopeEnd * 3600, 6);166 });167});168169describe("windows", () => {170 it("today/ytd windows subtract the model value at window start", () => {171 const m = base({ rateFn: { kind: "linear", perSecond: 1 } });172 const t = Date.parse("2026-03-10T06:00:00Z");173 expect(windowValue(m, t, "today")).toBeCloseTo(6 * 3600, 6);174 expect(windowValue(m, t, "ytd")).toBeCloseTo((t - t0) / 1000, 6);175 expect(windowValue(m, t, "session", t - 90_000)).toBeCloseTo(90, 9);176 });177178 it("UTC boundaries are correct", () => {179 const t = Date.parse("2026-03-10T06:07:08Z");180 expect(new Date(startOfUtcDay(t)).toISOString()).toBe("2026-03-10T00:00:00.000Z");181 expect(new Date(startOfUtcYear(t)).toISOString()).toBe("2026-01-01T00:00:00.000Z");182 });183});184