/* * **창에 다 들어오지 않은 발전 기간을 조용히 버리지 않는다.** * * ── 무엇이 일어났나 (2026-08-30) ────────────────────────────────────────── * 어제 발전량이 저널에 멀쩡히 있는데 화면이 비어 있었다. 발전 기간은 하루 단위이고(`08-28 15:00 → * 08-29 15:00`), 성과 구간은 「지금부터 24시간 전」이라 한낮에서 시작한다(`08-29 02:53 → 08-30 02:53`). * 하루 기간이 그 구간에 **온전히 들어오지 않는다.** * * 더하지 않는 것 자체는 맞다 — 걸친 기간을 더하면 다른 날의 값이 이 창의 값인 척한다. 틀린 것은 * **아무 말도 하지 않은 것**이다. 사용자는 빈 화면을 「발전량이 없다」로 읽는다. 그 둘은 완전히 * 다른 상황이다. */ import { test } from 'node:test' import assert from 'node:assert/strict' import { foldKpi } from '../server/engine/kpi-fold.ts' const T = (iso: string) => Date.parse(iso) /** 실측값 — 오창의 8월 29일 하루. */ const DAY_START = '2026-08-28T15:00:00.000Z' const DAY_END = '2026-08-29T15:00:00.000Z' const period = (equipmentId: string, kWh: number) => ({ eventType: 'energy.generation.period', eventTime: DAY_END, payload: { eventId: `id|${equipmentId}`, data: { equipmentId, kWh, periodStart: DAY_START, periodEnd: DAY_END, observedFrom: DAY_START, observedTo: DAY_END, accumulation: 'daily', boundary: 'offset' } } }) const EVENTS = [period('002', 127.1), period('003', 104.1), period('004', 312.4), period('005', 102.3)] as any test('★ 창이 기간을 온전히 담으면 더한다', () => { const r = foldKpi(EVENTS, { fromMs: T(DAY_START), toMs: T(DAY_END) }) const g = r.energy?.generated assert.ok(g, '발전량이 있어야 한다') assert.equal(g.counted, 4) assert.equal(Math.round(g.kWh * 10) / 10, 645.9) assert.equal(g.notCounted, undefined, '버린 것이 없으면 말하지 않는다') }) test('★ 창이 기간을 반만 담으면 더하지 않되 **버렸다고 말한다**', () => { /* 실제로 났던 구간 — 하루 기간의 한가운데에서 시작한다. */ const r = foldKpi(EVENTS, { fromMs: T('2026-08-29T02:53:00.000Z'), toMs: T('2026-08-30T02:53:00.000Z') }) const g = r.energy?.generated assert.ok(g, '값이 없다고 아무것도 내지 않으면 화면은 「발전량 없음」으로 읽는다') assert.equal(g.kWh, 0, '걸친 기간은 더하지 않는다 — 더하면 다른 날 값이 섞인다') assert.equal(g.counted, 0) assert.ok(g.notCounted, '버린 것을 말해야 한다') assert.equal(g.notCounted.periods, 4) assert.equal(Math.round(g.notCounted.kWh * 10) / 10, 645.9, '얼마를 못 세었는지 말한다') assert.equal(g.notCounted.earliestStart, DAY_START) assert.equal(g.notCounted.latestEnd, DAY_END) }) test('창이 기간과 아예 겹치지 않으면 말할 것이 없다 — 없는 것을 만들지 않는다', () => { const r = foldKpi(EVENTS, { fromMs: T('2026-07-01T00:00:00.000Z'), toMs: T('2026-07-02T00:00:00.000Z') }) assert.equal(r.energy?.generated, undefined) }) test('온전히 든 것과 걸친 것이 섞이면 더한 것과 못 센 것을 함께 낸다', () => { const earlier = { ...period('002', 100), eventTime: '2026-08-28T15:00:00.000Z', payload: { eventId: 'id|earlier', data: { equipmentId: '002', kWh: 100, periodStart: '2026-08-27T15:00:00.000Z', periodEnd: '2026-08-28T15:00:00.000Z', observedFrom: '2026-08-27T15:00:00.000Z', observedTo: '2026-08-28T15:00:00.000Z', accumulation: 'daily', boundary: 'offset' } } } const r = foldKpi([earlier, ...EVENTS] as any, { fromMs: T('2026-08-27T15:00:00.000Z'), toMs: T('2026-08-29T02:00:00.000Z') }) const g = r.energy?.generated assert.ok(g) assert.equal(g.counted, 1, '27→28 하루만 온전히 들었다') assert.equal(g.kWh, 100) assert.equal(g.notCounted?.periods, 4, '28→29 넷은 창의 끝을 넘는다') })