vpp-ai-platform/packages/services/test/timeseries.test.ts

79 lines
3.3 KiB
TypeScript
Raw Permalink Normal View History

import { describe, expect, it } from 'vitest'
import type { Curve96 } from '@vpp/domain'
import { MemoryTimeSeriesStore } from '../src/timeseries.js'
const curve = (date: string, fill = '1.0'): Curve96 => ({
interval_minutes: 15,
date,
values: Array.from({ length: 96 }, () => fill),
})
let tick = 0
const clock = () => `2026-03-14T08:00:${String(tick++).padStart(2, '0')}Z`
describe('time-series store', () => {
it('writes a revision and reads it back as latest', () => {
const store = new MemoryTimeSeriesStore(clock)
const rec = store.write('load:agg-01', curve('2026-03-14'), 'meter-file')
expect(rec.version).toBe(1)
expect(store.latest('load:agg-01', '2026-03-14')).toEqual(rec)
expect(store.latest('load:agg-01', '2026-03-15')).toBeUndefined()
})
it('keeps revision history instead of overwriting (late corrections)', () => {
const store = new MemoryTimeSeriesStore(clock)
store.write('load:agg-01', curve('2026-03-14', '1.0'), 'edge')
const corrected = store.write('load:agg-01', curve('2026-03-14', '1.1'), 'settlement-meter')
expect(corrected.version).toBe(2)
const history = store.revisions('load:agg-01', '2026-03-14')
expect(history.map((r) => r.version)).toEqual([1, 2])
expect(history[0]!.curve.values[0]).toBe('1.0')
expect(store.latest('load:agg-01', '2026-03-14')!.curve.values[0]).toBe('1.1')
})
it('is idempotent for identical content', () => {
const store = new MemoryTimeSeriesStore(clock)
const a = store.write('pv:site-7', curve('2026-03-14'), 'edge')
const b = store.write('pv:site-7', curve('2026-03-14'), 'edge-retry')
expect(b).toBe(a)
expect(store.revisions('pv:site-7', '2026-03-14')).toHaveLength(1)
})
it('returns an inclusive date range with latest revision per date, sorted', () => {
const store = new MemoryTimeSeriesStore(clock)
store.write('load:agg-01', curve('2026-03-16'), 'edge')
store.write('load:agg-01', curve('2026-03-14', '1.0'), 'edge')
store.write('load:agg-01', curve('2026-03-14', '2.0'), 'edge')
store.write('load:agg-01', curve('2026-03-13'), 'edge')
store.write('load:agg-01', curve('2026-03-17'), 'edge')
store.write('pv:site-7', curve('2026-03-15'), 'edge')
const out = store.range('load:agg-01', '2026-03-14', '2026-03-16')
expect(out.map((r) => r.date)).toEqual(['2026-03-14', '2026-03-16'])
expect(out[0]!.version).toBe(2)
})
it('rejects malformed curves and empty series ids', () => {
const store = new MemoryTimeSeriesStore(clock)
expect(() => store.write('', curve('2026-03-14'), 'edge')).toThrow(/series_id/)
expect(() =>
store.write('load:x', { ...curve('2026-03-14'), values: ['1.0'] }, 'edge'),
).toThrow()
expect(() => store.range('load:x', '2026/03/14', '2026-03-15')).toThrow()
})
it('stored curves are isolated from caller mutation', () => {
const store = new MemoryTimeSeriesStore(clock)
const input = curve('2026-03-14')
store.write('load:x', input, 'edge')
input.values[0] = '999'
expect(store.latest('load:x', '2026-03-14')!.curve.values[0]).toBe('1.0')
})
it('lists series ids', () => {
const store = new MemoryTimeSeriesStore(clock)
store.write('pv:site-7', curve('2026-03-14'), 'edge')
store.write('load:agg-01', curve('2026-03-14'), 'edge')
expect(store.seriesIds()).toEqual(['load:agg-01', 'pv:site-7'])
})
})