Files
homeclaw/tests/kma-grid.test.ts
kimandClaude Opus 5 191ca2cd0d feat: 강수 사상 누적을 ASOS 실측으로 전환 + 격자 변환 +1 밀림 수정
ASOS 시간자료/일자료가 승인돼서, 사상 누적을 Open-Meteo 추정에서
기상청 관측으로 바꾼다. 두 소스의 창이 정확히 맞물린다 — ASOS는
"전날 자료까지"(오늘은 resultCode 99), 초단기실황은 "최근 1일"이라
자정에서 나누면 사상 전체가 관측값이 된다.

rn 인코딩 주의: ""=무강수, "0.0"=미량(관측됐으나 0.05mm 미만).
둘을 뭉개면 이슬비로 이어지던 비가 소강으로 잡혀 사상이 끊긴다.
그래서 findRainEpisode가 wet을 명시로 받는다(안 주면 종전 mm 임계).

지점 좌표는 일부러 없다. 최근접 지점 방식을 만들다 폐기했다:
지점명 지오코딩이 홍성·보령·밀양을 북한 동명 지역으로, 남원을
제주도로 돌려줬다. 검증하려고 ASOS 관측기온 vs Open-Meteo 기온을
대조했으나 좌표가 정확한 서울조차 Δ3.1°C(도시열섬·모델편차)라
판별자가 못 됐다. 이름 매칭만 쓰고 못 맞히면 Open-Meteo로 물러난다.
지점 목록 97개는 지점정보 API가 없어서(NO_OPENAPI_SERVICE) 일자료
엔드포인트로 stnIds 90~300을 전수조사해 실응답에서 뽑았다.

같이 고친 것: latLonToKmaGrid가 공식 변환식의 반올림 항을 +0.5가
아닌 +1.5로 써서 nx·ny를 나란히 한 칸씩(대각 ~7km) 밀어 조회했다.
이름이 KMA_GRID 표에 있는 도시는 표를 타서 멀쩡했고 좌표 입력만
틀려서 여태 안 드러났다. 같은 서울을 이름과 좌표로 조회했을 때
일 누적이 9.8mm vs 29mm로 갈리며 발견.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 12:13:09 +09:00

41 lines
1.9 KiB
TypeScript

/**
* kma-grid.test.ts
*
* 좌표 → 기상청 격자(nx, ny) 변환.
*
* 2026-08-16에 이 함수가 nx·ny를 나란히 한 칸씩 밀어서 조회하고 있었다는 걸 발견했다.
* 기상청 공식 변환식의 마지막 항은 반올림용 +0.5인데 코드엔 +1.5가 들어 있었다.
* 격자 1칸이 5km라 대각선으로 ~7km 떨어진 셀을 읽고 있었고, 같은 서울을 이름("서울")과
* 좌표("37.5665,126.978")로 조회했을 때 오늘 누적 강수량이 9.8mm vs 29mm로 갈리면서 드러났다.
*
* 이름이 KMA_GRID 표에 있는 도시는 표를 타므로 멀쩡했고, 좌표로 들어온 위치만 틀렸다.
* 그래서 몇 년 동안 아무도 못 봤다 — 아래 기대값은 그 표의 값이고, 변환식이 같은 답을
* 내는지 대조하는 게 이 테스트의 요지다.
*/
import { test, describe } from 'node:test';
import assert from 'node:assert/strict';
import { latLonToKmaGrid } from '../src/tools/weather';
describe('latLonToKmaGrid — 좌표에서 기상청 격자 좌표', () => {
// 기대값은 기상청이 배포하는 "동네예보 격자" 표의 값(= 코드 내 KMA_GRID 표와 동일).
const cases: Array<[string, number, number, number, number]> = [
['서울', 37.5665, 126.9780, 60, 127],
['부산', 35.1796, 129.0756, 98, 76],
['대전', 36.3504, 127.3845, 67, 100],
['구미', 36.1195, 128.3446, 84, 96],
];
for (const [name, lat, lon, nx, ny] of cases) {
test(`${name} (${lat}, ${lon}) → ${nx}, ${ny}`, () => {
assert.deepEqual(latLonToKmaGrid(lat, lon), { nx, ny });
});
}
test('+1 밀림 회귀 방어: 서울이 61,128로 나오면 안 된다', () => {
// 버그 당시의 정확한 출력. 상수를 되돌리면 이 테스트부터 깨진다.
const g = latLonToKmaGrid(37.5665, 126.978);
assert.notDeepEqual(g, { nx: 61, ny: 128 });
});
});