WMO weather_code 51~57(이슬비 3단계), 61~67(비/어는비 혼재) 등을 "이슬비"·"비" 같은 큰 구간으로 뭉쳐 표시하던 것을 코드 하나당 고유 라벨로 바꿨다. 서버 프리렌더 스냅샷(site_payload._WEATHER_CONDITION_BY_CODE)과 브라우저 재조회 (use-live-weather.ts WEATHER_CONDITION_BY_CODE)가 같은 표를 봐야 하이드레이션 전후로 문구가 안 바뀐다는 기존 불변식은 유지한다. - docs/WEATHER.md: 27개 코드 전체를 "구간→분류" 표에서 "코드→고유 라벨" 표로 재작성 - weather_notes.json: 코드별 문구 갱신 - use-live-weather.ts/.test.ts, weather.test.tsx: 새 라벨 반영
82 lines
5.5 KiB
Markdown
82 lines
5.5 KiB
Markdown
# 오늘의 날씨
|
||
|
||
`Open-Meteo → /v1/local/weather → useLiveWeather → WeatherSection`
|
||
|
||
관측값은 기존 API를 사용하며 브라우저에서 10분마다 갱신한다. 조회 실패 시 마지막 관측값과
|
||
관측 시각을 유지한다. 날씨 문구는 API 요청마다 생성하지 않는다. **API 키 없음** — Open-Meteo
|
||
는 키 발급 없이 쓰는 무료 공개 엔드포인트다(`services/external/open_meteo.py`).
|
||
|
||
## Open-Meteo 응답 → 내부 스냅샷
|
||
|
||
`GET https://api.open-meteo.com/v1/forecast?latitude=&longitude=¤t=temperature_2m,weather_code,wind_speed_10m&timezone=auto`
|
||
|
||
원본 `current` 블록(`temperature_2m`·`weather_code`·`wind_speed_10m`·`time`)을 어댑터가
|
||
`{temperature, weather_code, wind_speed, observed_at, timezone, latitude, longitude}`로
|
||
정규화한다(`open_meteo.py:fetch_current_weather`). `weather_code`는 WMO 표준 정수 코드 그대로
|
||
저장·전달되고, 하늘 상태 문구로 바꾸는 건 아래 두 곳뿐이다 — **반드시 같은 표여야 한다**
|
||
(하이드레이션 전엔 백엔드 값, 후엔 브라우저 값을 쓰는데 표가 다르면 같은 날씨인데 문구가 바뀐다):
|
||
|
||
- 서버: `site_payload._WEATHER_CONDITION_BY_CODE` (조회는 `_weather_condition()`) — 프리렌더 스냅샷에 쓰인다.
|
||
- 브라우저: `use-live-weather.ts:WEATHER_CONDITION_BY_CODE` (조회는 `condition()`) — 10분마다 재조회할 때 쓰인다.
|
||
|
||
둘 다 **코드마다 고유 라벨**을 반환하는 딕셔너리 조회다(구간 검사가 아니다) — 코드 하나가
|
||
분류 하나에 정확히 대응하므로 "51~57 은 다 이슬비" 식으로 뭉치지 않는다.
|
||
|
||
| 코드 | WMO 의미(영어) | 분류(=조건 라벨) |
|
||
|---|---|---|
|
||
| 0 | Clear sky | 맑음 |
|
||
| 1 | Mainly clear | 대체로 맑음 |
|
||
| 2 | Partly cloudy | 구름 조금 |
|
||
| 3 | Overcast | 흐림 |
|
||
| 45 | Fog | 안개 |
|
||
| 48 | Depositing rime fog | 착빙성 안개 |
|
||
| 51 | Drizzle: Light intensity | 가벼운 이슬비 |
|
||
| 53 | Drizzle: Moderate intensity | 보통 이슬비 |
|
||
| 55 | Drizzle: Dense intensity | 강한 이슬비 |
|
||
| 56 | Freezing Drizzle: Light intensity | 가벼운 착빙성 이슬비 |
|
||
| 57 | Freezing Drizzle: Dense intensity | 강한 착빙성 이슬비 |
|
||
| 61 | Rain: Slight intensity | 약한 비 |
|
||
| 63 | Rain: Moderate intensity | 보통 비 |
|
||
| 65 | Rain: Heavy intensity | 강한 비 |
|
||
| 66 | Freezing Rain: Light intensity | 약한 착빙성 비 |
|
||
| 67 | Freezing Rain: Heavy intensity | 강한 착빙성 비 |
|
||
| 71 | Snow fall: Slight intensity | 약한 눈 |
|
||
| 73 | Snow fall: Moderate intensity | 보통 눈 |
|
||
| 75 | Snow fall: Heavy intensity | 강한 눈 |
|
||
| 77 | Snow grains | 싸라기눈 |
|
||
| 80 | Rain showers: Slight | 약한 소나기 |
|
||
| 81 | Rain showers: Moderate | 보통 소나기 |
|
||
| 82 | Rain showers: Violent | 강한 소나기 |
|
||
| 85 | Snow showers: Slight | 약한 소나기눈 |
|
||
| 86 | Snow showers: Heavy | 강한 소나기눈 |
|
||
| 95 | Thunderstorm: Slight or moderate | 뇌우 |
|
||
| 96 | Thunderstorm with slight hail | 약한 우박 뇌우 |
|
||
| 99 | Thunderstorm with heavy hail | 강한 우박 뇌우 |
|
||
|
||
이 28개가 Open-Meteo `weather_code`의 전체 정의 값이다 — 표에 없는 값(파싱 실패 포함)만
|
||
안전하게 `흐림`으로 떨어진다(실제로는 도달하지 않는 방어 분기).
|
||
|
||
`weatherMood()`(`derive.ts`)는 위 28종을 화면 배경 그림용으로 다시 4종(맑음/흐림/비/눈)으로
|
||
뭉친다 — 정규식 기반이라 새 분류를 추가해도 대개 자동으로 걸린다(예: "가벼운 착빙성 이슬비"는
|
||
`/비|우|소나기/` 패턴에 "비"가 들어 있어 `비`로 걸리고, "약한 소나기눈"은 `/눈|설/` 이 먼저 걸려
|
||
`눈`이 된다 — 검사 순서가 그래서 중요하다). `WeatherSection`의 `skyKey`는 노트에 그 조건 키가
|
||
실제로 있으면(`notes?.noteSets?.[condition]`) 그 조건 그대로 쓰고, 없으면(옛 payload 등)
|
||
`mood`로 내려간다 — 화이트리스트를 따로 유지하지 않는 일반화된 조회다.
|
||
|
||
`weather_notes.json → weather_notes.py → site_payload._weather → noteSets/tempNoteSets`
|
||
|
||
하늘 28종(위 표의 "분류" 열 전체)·기온 5구간에 각 5문구를 싣는다(28×5+5×5=165줄, 전부
|
||
고유해야 순환이 막히지 않는다). 첫 렌더는 첫 문장으로 고정해 하이드레이션을 맞추고,
|
||
브라우저에서는 무작위 시작 후 20초마다 하늘·기온 두 줄을 한 타이머로 같이 골라 한 바퀴 안에서
|
||
중복 없이 순환한다(`useWeatherNotes`). 기온 구간은 기존 `weatherBand`의 30·25·20·10도다.
|
||
|
||
**세분화 원칙**: 강도(약/보통/강)만 다른 코드도 문구를 따로 쓴다 — 약한 비는 "우산 하나면
|
||
충분", 강한 비는 "이동을 미루라"처럼 안내 자체가 달라지기 때문이다. 착빙성(어는 비/이슬비)은
|
||
안개·비·이슬비와 별도로 갈랐다 — 노면 결빙이라는, 세기와는 다른 축의 위험이라 "도로가
|
||
얼어붙을 수 있으니" 식의 안전 안내가 필요하다(2026-09-18).
|
||
|
||
목업 README 2.3의 순환 계약을 제품으로 옮겼다. 군산 전용 시설·장소를 다른 사업장에 복사하지
|
||
않도록 기본 문구는 장소·시설·영업시간을 주장하지 않는 공통 안내로 구성한다. **LLM 생성이 아니다.**
|
||
지역별 장소 추천을 자동 생성하는 작업은 포함하지 않았다. 목업의 수기 문구·산출물은 변경하지 않는다.
|
||
옛 단일 `note`·`notes`·`tempNotes` payload도 계속 지원한다. 이미 발행된 사이트는 재발행해야 반영된다.
|