/** * weather-kma-daily-rain.test.ts * * weather_kma의 초단기실황은 오랫동안 RN1(직전 1시간 강수량)만 내보냈다. 그래서 "오늘 비 * 얼마나 왔어?"를 물으면 모델이 — 틀린 말은 아니지만 쓸모없게 — "1시간 강수량만 있고 일 * 누적은 없어서 답할 수 없다"고 답했다(2026-08-16 실제 사례). * * 초단기실황이 같은 날 과거 base_time을 받아준다는 걸 라이브 API로 확인해서, 시간별 실측을 * 합쳐 일 누적을 만든다. 이 파일은 그 합산 로직 — 네트워크를 안 타는 순수 함수다. * * 핵심 규칙: RN1(HH00)은 HH00에 "끝나는" 1시간의 강수량이다. 따라서 오늘 00시~현재는 * base_time 0100..HH00의 합이고, 0000은 어제 23~24시라 빠져야 한다. 이 경계는 호출부 * (fetchKmaTodayRain)가 지키고, 여기서는 넘어온 값들의 합산·결측 처리만 검증한다. */ import { test, describe } from 'node:test'; import assert from 'node:assert/strict'; import { sumKmaHourlyRain } from '../src/tools/weather'; describe('sumKmaHourlyRain — 시간별 RN1 실측 합산', () => { test('숫자 문자열을 더해 일 누적을 낸다', () => { const { mm, missing } = sumKmaHourlyRain([ { hour: 1, rn1: '0' }, { hour: 2, rn1: '3.1' }, { hour: 3, rn1: '0' }, { hour: 4, rn1: '1.5' }, ]); assert.equal(mm, 4.6); assert.deepEqual(missing, []); }); test('부동소수 드리프트를 소수 1자리로 정리한다', () => { // 0.1 + 0.2 = 0.30000000000000004 — 관측값이 소수 1자리라 그냥 더하면 그대로 새어나온다. const { mm } = sumKmaHourlyRain([ { hour: 1, rn1: '0.1' }, { hour: 2, rn1: '0.2' }, ]); assert.equal(mm, 0.3); }); test('"강수없음"은 결측이 아니라 0으로 센다', () => { // 초단기실황은 숫자를 주지만 형제 엔드포인트들은 0을 "강수없음"으로 보낸다. // 이걸 결측으로 세면 멀쩡한 합계에 "결측" 딱지가 붙는다. const { mm, missing } = sumKmaHourlyRain([ { hour: 1, rn1: '강수없음' }, { hour: 2, rn1: '2.0' }, ]); assert.equal(mm, 2); assert.deepEqual(missing, []); }); test('null/빈문자는 결측으로 기록하되 합산은 계속한다', () => { // 한 시각이 NO_DATA로 빠져도 나머지 합계는 살아야 한다 — 대신 부분합임을 알려야 한다. const { mm, missing } = sumKmaHourlyRain([ { hour: 1, rn1: '1.0' }, { hour: 2, rn1: null }, { hour: 3, rn1: '' }, { hour: 4, rn1: undefined }, { hour: 5, rn1: '2.0' }, ]); assert.equal(mm, 3); assert.deepEqual(missing, [2, 3, 4]); }); test('전부 결측이면 0mm에 결측 목록만 남는다', () => { // 호출부는 이 경우를 "조회 불가"로 바꿔서 0mm를 사실처럼 말하지 않게 한다. const { mm, missing } = sumKmaHourlyRain([ { hour: 1, rn1: null }, { hour: 2, rn1: null }, ]); assert.equal(mm, 0); assert.deepEqual(missing, [1, 2]); }); test('빈 입력은 0mm', () => { assert.deepEqual(sumKmaHourlyRain([]), { mm: 0, missing: [] }); }); });