실제 사고(2026-08-10): "이번 주말 날씨 어때?"(5일 후 질문)에 weather_kma만 불렀고, 기상청 단기예보가 3일까지만 커버한다는 걸 확인하자 "금요일쯤 다시 물어봐 주세요"라며 끝냈다. weather_kma 자체 설명에 "3일 넘으면 weather_openmeteo로 보완할 것"이라고 이미 적혀 있었는데도 두 번째 도구를 안 불렀다. 같은 날 news_search에서 겪은 것과 정확히 같은 패턴 — 프롬프트/ 스키마 설명 지침을 아무리 정확히 적어도 이 모델은 세부 지시를 안 따랐다. 그래서 같은 해법을 적용했다: 모델이 두 번째 도구를 기억해서 불러주길 바라는 대신, weather_kma가 자기 응답 안에서 Open-Meteo 4~7일차 예보(기온·강수확률)를 자동으로 붙여서 반환한다. 한 번의 호출로 끝나므로 두 번째 호출을 빼먹을 여지가 없다. 기상청이 실제로 커버한 마지막 날짜 이후만 보완하도록 날짜 경계를 계산하고(Open-Meteo가 이미 아는 날짜를 중복 표시하지 않음), 네트워크 실패 시엔 조용히 생략(기상청 데이터는 그대로 반환) — 보완 기능 하나가 핵심 예보 응답을 절대 깨뜨리지 않는다. 순수 필터링/포맷 로직(formatDailySupplement)을 네트워크 호출과 분리해 테스트 가능하게 했다 — link-validator.ts/news.ts와 같은 패턴, 회귀 테스트 4개 추가. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
51 lines
2.7 KiB
TypeScript
51 lines
2.7 KiB
TypeScript
/**
|
|
* weather-kma-supplement.test.ts
|
|
*
|
|
* formatDailySupplement was split out of weather_kma's forecast path (2026-08-10) after a real
|
|
* incident: "이번 주말 날씨 어때?" (5 days out) hit weather_kma's ~3-day ceiling, and the model
|
|
* told the user to ask again later instead of calling weather_openmeteo — even though
|
|
* weather_kma's own description already said to do exactly that for >3-day requests. Prose
|
|
* guidance kept failing to change this model's behavior (same pattern hit news_search earlier
|
|
* the same day), so weather_kma now fetches the Open-Meteo extension itself and merges it in.
|
|
* This pure function is the merge/filter logic — no network call, so it's directly testable.
|
|
*/
|
|
|
|
import { test, describe } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { formatDailySupplement } from '../src/tools/weather';
|
|
|
|
describe('formatDailySupplement — KMA 3일 이후를 Open-Meteo로 보완', () => {
|
|
test('KMA가 8/10까지 커버했으면 그 이후 날짜만 남긴다', () => {
|
|
const dates = ['2026-08-09', '2026-08-10', '2026-08-11', '2026-08-12', '2026-08-15'];
|
|
const tmax = [33, 34, 32, 31, 29];
|
|
const tmin = [24, 25, 23, 22, 21];
|
|
const pop = [20, 10, 30, 40, 60];
|
|
const out = formatDailySupplement(dates, tmax, tmin, pop, '20260810');
|
|
assert.ok(out, '보완 결과가 없으면 안 됨');
|
|
assert.ok(!out!.some(l => l.includes('2026-08-09')), 'KMA가 이미 커버한 과거 날짜가 남으면 안 됨');
|
|
assert.ok(!out!.some(l => l.includes('2026-08-10')), 'KMA가 이미 커버한 마지막 날짜가 남으면 안 됨');
|
|
assert.ok(out!.some(l => l.includes('2026-08-11')));
|
|
assert.ok(out!.some(l => l.includes('2026-08-15')), '이번 주말(8/15) 데이터가 포함돼야 함');
|
|
});
|
|
|
|
test('KMA가 이미 전체 범위를 커버했으면(추가 날짜 없음) null', () => {
|
|
const dates = ['2026-08-09', '2026-08-10'];
|
|
const out = formatDailySupplement(dates, [33, 34], [24, 25], [20, 10], '20260810');
|
|
assert.equal(out, null);
|
|
});
|
|
|
|
test('lastCoveredYmd가 비어 있으면(경계 모름) 전체를 보완으로 취급', () => {
|
|
const dates = ['2026-08-10', '2026-08-11'];
|
|
const out = formatDailySupplement(dates, [34, 32], [25, 23], [10, 30], '');
|
|
assert.ok(out);
|
|
assert.equal(out!.filter(l => l.startsWith('📅')).length, 2);
|
|
});
|
|
|
|
test('강수확률이 없어도(null) 깨지지 않는다', () => {
|
|
const out = formatDailySupplement(['2026-08-15'], [29], [21], [null], '20260810');
|
|
assert.ok(out);
|
|
assert.ok(!out!.some(l => l.includes('강수확률')), 'null 강수확률은 줄 자체를 안 넣어야 함');
|
|
assert.ok(out!.some(l => l.includes('기온: 21~29°C')));
|
|
});
|
|
});
|