feat(backend): 기상청 단기예보와 공휴일 표 추가 #12

Merged
ttipo merged 1 commits from feat/weather-holidays into main 2026-11-08 16:25:18 +09:00
Owner

무엇을 · 왜

AI 코스 생성의 조건 해석과 후보 거르기(설계 §3.3)에 그날 날씨와 공휴일이 필요하다. 구현 순서의 3단계(설계 §14)다.

  • 강수확률이 40% 이상이고 사용자가 '비 오면 실내'를 골랐으면 실내 위주로 짠다(2단계 시간표 로직이 이미 받는다)
  • 그날이 공휴일·명절이면 그날 쉬는 곳을 후보에서 뺀다

아직 API 에는 연결하지 않았다. 생성 작업(4단계)이 WeatherService 와 공휴일 표를 쓴다.

공휴일 표 — backend/src/shared/utils/kr-holidays.ts

  • 2026~2027년 46일을 넣었다. 설·추석 연휴, 대체공휴일, 2026년 지방선거일(6월 3일)이 들어간다
  • 2026년부터 노동절(5월 1일)과 제헌절(7월 17일)이 관공서 공휴일이다(2026-04-28 국무회의 의결). 두 날도 주말·다른 공휴일과 겹치면 대체공휴일이 붙어, 2027년 5월 3일·7월 19일이 대체공휴일이다
  • 해마다 갱신한다. 표에 없는 해는 isHolidayTableYear 가 false 라 쓰는 쪽이 경고를 남길 수 있다
  • 대체공휴일 규칙(주말과 겹친 공휴일 → 다음 첫 평일, 설·추석 연휴가 일요일과 겹침 → 연휴 다음 날)을 테스트로 검사한다. 표를 고칠 때 빠뜨린 날을 잡는다

출처: 뉴시스 — 인사처 '관공서의 공휴일 규정' 개정안 국무회의 의결, 머니투데이 — 노동절·제헌절 올해부터 공휴일, 공휴일 목록 2026 · 2027

공휴일에 장소 거르기 — course-generation/planner/filter.ts

  • 예전에는 공휴일이면 휴무 표시가 하나라도 있는 곳을 모두 뺐다
  • 이제는 그날 공휴일의 종류에 맞는 휴무만 본다(closedOnHoliday)
    • '공휴일' 휴무는 모든 공휴일에 뺀다
    • 설·추석 휴무는 그 연휴 내내(대체공휴일 포함) 뺀다. 당일만 쉬는지 알 수 없어서, 닫힌 곳으로 보내지 않는 쪽을 골랐다
    • 그래서 "설날·추석 당일 휴관"인 곳을 어린이날에는 빼지 않는다
  • PlanContext.holiday 가 boolean 에서 공휴일 종류(HolidayKind | null)로 바뀌었다

날씨 — backend/src/modules/weather/

파일 하는 일
kma-grid.ts 위경도 → 기상청 5km 격자(공개 LCC 공식). 대구 도심은 10월 점검 때와 같은 (89, 90)
kma-forecast.ts 발표 시각 고르기, 응답 해석, 시간 범위 요약(아래 목록)
kma-forecast.client.ts 공공데이터 공통 클라이언트로 getVilageFcst 를 부른다(아래 목록)
weather.service.ts 캐시와 호출 상한, 실패 처리(아래 목록)

발표 시각 고르기와 요약 (kma-forecast.ts)

  • 발표 10분 뒤부터 그 발표를 쓴다
  • 0214시 발표는 글피까지, 1723시 발표는 그글피까지 예보가 나온다
  • 시간 범위에서 가장 높은 강수확률과 그때의 강수 형태, 가장 잦은 하늘 상태, 최저·최고 기온을 낸다
  • 글피·그글피는 3시간 간격이라 한 예보 시각이 3시간을 맡는다
  • 예보가 시간 범위 끝까지 닿지 않으면 null 이다

호출 (kma-forecast.client.ts)

  • 사용자 요청 중에 부르므로 3초씩 2번까지만 시도한다
  • 결과 코드 03(자료 없음)은 오류가 아니라 '앞 발표로 받기'로 처리한다

캐시와 실패 처리 (weather.service.ts)

  • 같은 격자·발표는 1시간 동안 다시 부르지 않고, 동시에 같은 격자를 물으면 한 번만 부른다
  • 하루 호출은 1,000번까지 센다
  • 실패하면 던지지 않고 null 을 돌려준다(입력 화면은 '예보 없음'). 실패는 2분 동안 다시 부르지 않는다
  • 로그에는 사용자 위치 대신 5km 격자만 남긴다

키는 장소 동기화에 쓰던 PUBLIC_DATA_SERVICE_KEY 를 그대로 쓰고, 없으면 날씨만 '예보 없음'이다.

함께 바꾼 것

  • place/sync/public-data.client.ts: 호출 제한 시간·시도 횟수를 설정할 수 있게 했다. 기존 동기화는 그대로 20초·3번이다
  • src/commands/weather-check.ts + npm run weather:check: 실제 호출 확인 명령. 오늘부터 4일 뒤까지 날마다 시간 범위 요약을 보여 준다
  • 설계 docs/design/ai-course.md: §3.3(공휴일·날씨), §3.7(weather 필드), §11(키), §14 에 적었다

확인 방법

cd backend && npx jest src/modules/weather src/shared/utils/kr-holidays.spec.ts src/modules/course-generation
# 실제 호출(키 필요)
docker exec wandergo-backend npm run weather:check -- [--lat=35.87021 --lng=128.59409] [--start=10:00 --end=20:00]

2026-11-08 15:38 에 실제 호출을 확인했다.

  • 대구 도심 격자는 89,90 이다
  • 14시 발표로 11-08~11-11 예보가 나왔다. 11일은 3시간 간격 자료다
  • 11-12 는 예보 없음이다
  • 닷새치를 보는 동안 기상청 호출은 1번이었다

키 없이 띄우면 경고를 남기고 날마다 '예보 없음'으로 끝나는 것도 확인했다.

영향 범위

  • backend
  • frontend
  • flutter
  • docker / 인프라
  • 규칙 문서(.claude/)

공유 계약은 바뀌지 않는다. 새 API 가 없고, 날씨 모듈은 아직 앱 모듈에 연결하지 않았다. PlanContext.holiday 는 백엔드 안의 형식이다.

체크리스트

  • 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과: lint · tsc · 백엔드 단위 테스트 366개(새로 33개)
  • 주석 한국어, 매직 넘버·하드코딩 문자열 없음: 발표 시각·캐시 시간·호출 상한은 상수로 두었다
  • 사용자 노출 실패는 모두 에러 코드와 함께 처리: 해당 없음(날씨 실패는 '예보 없음'으로 화면에 보인다)
  • 에러 코드를 바꿨다면 npm run sync:error-codes 실행 후 생성 파일까지 커밋: 해당 없음
  • 민감정보 미커밋: 키는 오류 메시지에서도 가린다
  • 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음: 위치는 5km 격자로만 남긴다

🤖 Generated with Claude Code

## 무엇을 · 왜 AI 코스 생성의 조건 해석과 후보 거르기(설계 §3.3)에 그날 날씨와 공휴일이 필요하다. 구현 순서의 3단계(설계 §14)다. - 강수확률이 40% 이상이고 사용자가 '비 오면 실내'를 골랐으면 실내 위주로 짠다(2단계 시간표 로직이 이미 받는다) - 그날이 공휴일·명절이면 그날 쉬는 곳을 후보에서 뺀다 아직 API 에는 연결하지 않았다. 생성 작업(4단계)이 `WeatherService` 와 공휴일 표를 쓴다. ### 공휴일 표 — `backend/src/shared/utils/kr-holidays.ts` - 2026~2027년 46일을 넣었다. 설·추석 연휴, 대체공휴일, 2026년 지방선거일(6월 3일)이 들어간다 - **2026년부터 노동절(5월 1일)과 제헌절(7월 17일)이 관공서 공휴일이다**(2026-04-28 국무회의 의결). 두 날도 주말·다른 공휴일과 겹치면 대체공휴일이 붙어, 2027년 5월 3일·7월 19일이 대체공휴일이다 - 해마다 갱신한다. 표에 없는 해는 `isHolidayTableYear` 가 false 라 쓰는 쪽이 경고를 남길 수 있다 - 대체공휴일 규칙(주말과 겹친 공휴일 → 다음 첫 평일, 설·추석 연휴가 일요일과 겹침 → 연휴 다음 날)을 테스트로 검사한다. 표를 고칠 때 빠뜨린 날을 잡는다 출처: [뉴시스 — 인사처 '관공서의 공휴일 규정' 개정안 국무회의 의결](https://mobile.newsis.com/view/NISX20260428_0003608893), [머니투데이 — 노동절·제헌절 올해부터 공휴일](https://www.mt.co.kr/policy/2026/04/28/2026042808250174216), 공휴일 목록 [2026](https://kholidayz.com/year/2026) · [2027](https://kholidayz.com/year/2027) ### 공휴일에 장소 거르기 — `course-generation/planner/filter.ts` - 예전에는 공휴일이면 휴무 표시가 하나라도 있는 곳을 모두 뺐다 - 이제는 그날 공휴일의 종류에 맞는 휴무만 본다(`closedOnHoliday`) - '공휴일' 휴무는 모든 공휴일에 뺀다 - 설·추석 휴무는 그 연휴 내내(대체공휴일 포함) 뺀다. 당일만 쉬는지 알 수 없어서, 닫힌 곳으로 보내지 않는 쪽을 골랐다 - 그래서 "설날·추석 당일 휴관"인 곳을 어린이날에는 빼지 않는다 - `PlanContext.holiday` 가 `boolean` 에서 공휴일 종류(`HolidayKind | null`)로 바뀌었다 ### 날씨 — `backend/src/modules/weather/` | 파일 | 하는 일 | | --- | --- | | `kma-grid.ts` | 위경도 → 기상청 5km 격자(공개 LCC 공식). 대구 도심은 10월 점검 때와 같은 (89, 90) | | `kma-forecast.ts` | 발표 시각 고르기, 응답 해석, 시간 범위 요약(아래 목록) | | `kma-forecast.client.ts` | 공공데이터 공통 클라이언트로 `getVilageFcst` 를 부른다(아래 목록) | | `weather.service.ts` | 캐시와 호출 상한, 실패 처리(아래 목록) | **발표 시각 고르기와 요약** (`kma-forecast.ts`) - 발표 10분 뒤부터 그 발표를 쓴다 - 02~14시 발표는 글피까지, 17~23시 발표는 그글피까지 예보가 나온다 - 시간 범위에서 가장 높은 강수확률과 그때의 강수 형태, 가장 잦은 하늘 상태, 최저·최고 기온을 낸다 - 글피·그글피는 3시간 간격이라 한 예보 시각이 3시간을 맡는다 - 예보가 시간 범위 끝까지 닿지 않으면 null 이다 **호출** (`kma-forecast.client.ts`) - 사용자 요청 중에 부르므로 3초씩 2번까지만 시도한다 - 결과 코드 03(자료 없음)은 오류가 아니라 '앞 발표로 받기'로 처리한다 **캐시와 실패 처리** (`weather.service.ts`) - 같은 격자·발표는 1시간 동안 다시 부르지 않고, 동시에 같은 격자를 물으면 한 번만 부른다 - 하루 호출은 1,000번까지 센다 - **실패하면 던지지 않고 null** 을 돌려준다(입력 화면은 '예보 없음'). 실패는 2분 동안 다시 부르지 않는다 - 로그에는 사용자 위치 대신 5km 격자만 남긴다 키는 장소 동기화에 쓰던 `PUBLIC_DATA_SERVICE_KEY` 를 그대로 쓰고, 없으면 날씨만 '예보 없음'이다. ### 함께 바꾼 것 - `place/sync/public-data.client.ts`: 호출 제한 시간·시도 횟수를 설정할 수 있게 했다. 기존 동기화는 그대로 20초·3번이다 - `src/commands/weather-check.ts` + `npm run weather:check`: 실제 호출 확인 명령. 오늘부터 4일 뒤까지 날마다 시간 범위 요약을 보여 준다 - 설계 `docs/design/ai-course.md`: §3.3(공휴일·날씨), §3.7(`weather` 필드), §11(키), §14 에 적었다 ## 확인 방법 ```bash cd backend && npx jest src/modules/weather src/shared/utils/kr-holidays.spec.ts src/modules/course-generation # 실제 호출(키 필요) docker exec wandergo-backend npm run weather:check -- [--lat=35.87021 --lng=128.59409] [--start=10:00 --end=20:00] ``` 2026-11-08 15:38 에 실제 호출을 확인했다. - 대구 도심 격자는 89,90 이다 - 14시 발표로 11-08~11-11 예보가 나왔다. 11일은 3시간 간격 자료다 - 11-12 는 예보 없음이다 - 닷새치를 보는 동안 기상청 호출은 1번이었다 키 없이 띄우면 경고를 남기고 날마다 '예보 없음'으로 끝나는 것도 확인했다. ## 영향 범위 - [x] backend - [ ] frontend - [ ] flutter - [ ] docker / 인프라 - [ ] 규칙 문서(.claude/) > 공유 계약은 바뀌지 않는다. 새 API 가 없고, 날씨 모듈은 아직 앱 모듈에 연결하지 않았다. `PlanContext.holiday` 는 백엔드 안의 형식이다. ## 체크리스트 - [x] 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과: lint · tsc · 백엔드 단위 테스트 366개(새로 33개) - [x] 주석 한국어, 매직 넘버·하드코딩 문자열 없음: 발표 시각·캐시 시간·호출 상한은 상수로 두었다 - [x] 사용자 노출 실패는 모두 에러 코드와 함께 처리: 해당 없음(날씨 실패는 '예보 없음'으로 화면에 보인다) - [x] 에러 코드를 바꿨다면 `npm run sync:error-codes` 실행 후 생성 파일까지 커밋: 해당 없음 - [x] 민감정보 미커밋: 키는 오류 메시지에서도 가린다 - [x] 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음: 위치는 5km 격자로만 남긴다 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ttipo added 1 commit 2026-11-08 15:40:36 +09:00
feat(backend): 기상청 단기예보와 공휴일 표 추가
CI / Frontend (lint · type-check · test) (pull_request) Successful in 11m5s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m31s
CI / Backend (lint · test · e2e · build) (pull_request) Successful in 12m9s
CI / Frontend (lint · type-check · test) (push) Successful in 11m12s
CI / Backend (lint · test · e2e · build) (push) Successful in 11m46s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 11m8s
187a99ca7f
AI 코스 조건 해석·거르기(설계 §3.3)에 그날 날씨와 공휴일이 필요하다. 강수확률 40% 이상이면 실내 위주로 짜고,
공휴일·명절에 쉬는 곳은 후보에서 뺀다. 4단계(API 골격)에서 생성 작업에 연결한다.

- 공휴일 표(2026–2027): 설·추석 연휴·대체공휴일·지방선거일, 2026년부터 공휴일이 된 노동절·제헌절(대체공휴일 포함).
  대체공휴일 규칙을 테스트로 검사해 해마다 표를 갱신할 때 빠뜨린 날을 잡는다
- 공휴일 거르기를 명절 종류에 맞췄다 — 설·추석 당일 휴관인 곳을 어린이날에 빼지 않고, '공휴일' 휴관은 모든 공휴일에 뺀다
- 날씨: 위경도 → 5km 격자, 가장 최근 발표(자료가 아직 없으면 앞 발표), 시간 범위의 최고 강수확률·강수 형태·하늘·기온.
  1시간 캐시, 3초·2번 시도, 하루 1,000번, 실패하면 null(입력 화면은 '예보 없음')
- 공공데이터 공통 클라이언트에 호출 제한 시간·시도 횟수 설정을 더했다(기존 동기화는 그대로 20초·3번)
- 실제 호출 확인 명령 weather:check

2026-11-08 실제 호출: 대구 도심 격자 89,90, 14시 발표로 11-08~11-11 예보, 11-12 는 예보 없음(호출 1번).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ttipo merged commit 187a99ca7f into main 2026-11-08 16:25:18 +09:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ttipo/wandergo#12