[fix] site,solution/backend: 날씨 상태 라벨을 코드별 고유값으로 — 구간 뭉치기 제거
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: 새 라벨 반영
This commit is contained in:
parent
47da2f29b3
commit
cd771719df
@ -13,60 +13,67 @@
|
|||||||
원본 `current` 블록(`temperature_2m`·`weather_code`·`wind_speed_10m`·`time`)을 어댑터가
|
원본 `current` 블록(`temperature_2m`·`weather_code`·`wind_speed_10m`·`time`)을 어댑터가
|
||||||
`{temperature, weather_code, wind_speed, observed_at, timezone, latitude, longitude}`로
|
`{temperature, weather_code, wind_speed, observed_at, timezone, latitude, longitude}`로
|
||||||
정규화한다(`open_meteo.py:fetch_current_weather`). `weather_code`는 WMO 표준 정수 코드 그대로
|
정규화한다(`open_meteo.py:fetch_current_weather`). `weather_code`는 WMO 표준 정수 코드 그대로
|
||||||
저장·전달되고, 하늘 상태 문구로 바꾸는 건 아래 두 곳뿐이다 — **반드시 같은 구간이어야 한다**
|
저장·전달되고, 하늘 상태 문구로 바꾸는 건 아래 두 곳뿐이다 — **반드시 같은 표여야 한다**
|
||||||
(하이드레이션 전엔 백엔드 값, 후엔 브라우저 값을 쓰는데 표가 다르면 같은 날씨인데 문구가 바뀐다):
|
(하이드레이션 전엔 백엔드 값, 후엔 브라우저 값을 쓰는데 표가 다르면 같은 날씨인데 문구가 바뀐다):
|
||||||
|
|
||||||
- 서버: `site_payload._weather_condition()` — 프리렌더 스냅샷에 쓰인다.
|
- 서버: `site_payload._WEATHER_CONDITION_BY_CODE` (조회는 `_weather_condition()`) — 프리렌더 스냅샷에 쓰인다.
|
||||||
- 브라우저: `use-live-weather.ts:condition()` — 10분마다 재조회할 때 쓰인다.
|
- 브라우저: `use-live-weather.ts:WEATHER_CONDITION_BY_CODE` (조회는 `condition()`) — 10분마다 재조회할 때 쓰인다.
|
||||||
|
|
||||||
| 코드 | 날씨 상태(한국어) | 날씨 상태(영어) | 내부 분류 |
|
둘 다 **코드마다 고유 라벨**을 반환하는 딕셔너리 조회다(구간 검사가 아니다) — 코드 하나가
|
||||||
|---|---|---|---|
|
분류 하나에 정확히 대응하므로 "51~57 은 다 이슬비" 식으로 뭉치지 않는다.
|
||||||
| 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 | 뇌우 |
|
|
||||||
|
|
||||||
이 27개가 Open-Meteo `weather_code`의 전체 정의 값이다 — 표에 없는 값(파싱 실패 포함)만
|
| 코드 | 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`)는 위 11종을 화면 배경 그림용으로 다시 4종(맑음/흐림/비/눈)으로
|
`weatherMood()`(`derive.ts`)는 위 28종을 화면 배경 그림용으로 다시 4종(맑음/흐림/비/눈)으로
|
||||||
뭉친다 — 정규식 기반이라 새 분류를 추가해도 대개 자동으로 걸린다(예: "이슬비"·"강한비"·"어는비"는
|
뭉친다 — 정규식 기반이라 새 분류를 추가해도 대개 자동으로 걸린다(예: "가벼운 착빙성 이슬비"는
|
||||||
모두 `/비|우/` 패턴에 "비"가 들어 있어 `비`로 걸린다). `WeatherSection`의 `skyKey`는 노트가
|
`/비|우|소나기/` 패턴에 "비"가 들어 있어 `비`로 걸리고, "약한 소나기눈"은 `/눈|설/` 이 먼저 걸려
|
||||||
실제로 있는 세부 조건(구름많음/안개/소나기/뇌우/이슬비/강한비/어는비)이면 그 조건 그대로 쓰고,
|
`눈`이 된다 — 검사 순서가 그래서 중요하다). `WeatherSection`의 `skyKey`는 노트에 그 조건 키가
|
||||||
그 외(맑음/흐림/비/눈)엔 `mood`로 내려간다 — 어차피 세부 조건과 mood 값이 같기 때문이다.
|
실제로 있으면(`notes?.noteSets?.[condition]`) 그 조건 그대로 쓰고, 없으면(옛 payload 등)
|
||||||
|
`mood`로 내려간다 — 화이트리스트를 따로 유지하지 않는 일반화된 조회다.
|
||||||
|
|
||||||
`weather_notes.json → weather_notes.py → site_payload._weather → noteSets/tempNoteSets`
|
`weather_notes.json → weather_notes.py → site_payload._weather → noteSets/tempNoteSets`
|
||||||
|
|
||||||
하늘 11종(맑음·구름많음·안개·비·이슬비·강한비·어는비·눈·소나기·뇌우·흐림)·기온 5구간에 각
|
하늘 28종(위 표의 "분류" 열 전체)·기온 5구간에 각 5문구를 싣는다(28×5+5×5=165줄, 전부
|
||||||
5문구를 싣는다. 첫 렌더는 첫 문장으로 고정해 하이드레이션을 맞추고, 브라우저에서는 무작위 시작
|
고유해야 순환이 막히지 않는다). 첫 렌더는 첫 문장으로 고정해 하이드레이션을 맞추고,
|
||||||
후 20초마다 한 바퀴 안에서 중복 없이 순환한다. 기온 구간은 기존 `weatherBand`의 30·25·20·10도다.
|
브라우저에서는 무작위 시작 후 20초마다 하늘·기온 두 줄을 한 타이머로 같이 골라 한 바퀴 안에서
|
||||||
구름많음·안개·소나기·뇌우·이슬비·강한비·어는비는 그림(mood)상 흐림 또는 비와 같이 묶이지만
|
중복 없이 순환한다(`useWeatherNotes`). 기온 구간은 기존 `weatherBand`의 30·25·20·10도다.
|
||||||
문구는 조건별로 따로 쓴다. **비 중에서도 어는비(착빙성)만 안전 안내가 다르다** — 도로·보행
|
|
||||||
결빙 위험을 짚는 문구가 필요해서 이슬비·강한비와 갈랐다(2026-09-17).
|
**세분화 원칙**: 강도(약/보통/강)만 다른 코드도 문구를 따로 쓴다 — 약한 비는 "우산 하나면
|
||||||
|
충분", 강한 비는 "이동을 미루라"처럼 안내 자체가 달라지기 때문이다. 착빙성(어는 비/이슬비)은
|
||||||
|
안개·비·이슬비와 별도로 갈랐다 — 노면 결빙이라는, 세기와는 다른 축의 위험이라 "도로가
|
||||||
|
얼어붙을 수 있으니" 식의 안전 안내가 필요하다(2026-09-18).
|
||||||
|
|
||||||
목업 README 2.3의 순환 계약을 제품으로 옮겼다. 군산 전용 시설·장소를 다른 사업장에 복사하지
|
목업 README 2.3의 순환 계약을 제품으로 옮겼다. 군산 전용 시설·장소를 다른 사업장에 복사하지
|
||||||
않도록 기본 문구는 장소·시설·영업시간을 주장하지 않는 공통 안내로 구성한다. **LLM 생성이 아니다.**
|
않도록 기본 문구는 장소·시설·영업시간을 주장하지 않는 공통 안내로 구성한다. **LLM 생성이 아니다.**
|
||||||
|
|||||||
@ -7,12 +7,19 @@
|
|||||||
"다음 일정을 서두르기보다 잠시 여유를 가져 보세요.",
|
"다음 일정을 서두르기보다 잠시 여유를 가져 보세요.",
|
||||||
"좋아하는 책 몇 장을 넘기며 쉬어 가는 건 어떨까요?"
|
"좋아하는 책 몇 장을 넘기며 쉬어 가는 건 어떨까요?"
|
||||||
],
|
],
|
||||||
"구름많음": [
|
"대체로 맑음": [
|
||||||
"구름이 많은 날에는 실내에서 다음 일정을 천천히 골라 보세요.",
|
"구름이 살짝 있지만 대체로 맑은 하루입니다. 나들이하기 좋은 날씨입니다.",
|
||||||
"여행 사진을 정리하며 잠깐 쉬어 가세요.",
|
"옅은 구름 사이로 볕이 듭니다. 가볍게 산책을 나서 보세요.",
|
||||||
"함께 온 사람과 오늘 가장 좋았던 순간을 이야기해 보세요.",
|
"하늘이 대체로 맑아 야외 일정을 편하게 잡을 수 있습니다.",
|
||||||
"바깥 일정 사이에 여유로운 휴식 시간을 넣어 보세요.",
|
"구름이 잠깐씩 지나가지만 큰 영향은 없습니다.",
|
||||||
"오늘의 기분을 짧은 글로 남겨도 좋겠습니다."
|
"맑은 편이니 오늘 담고 싶은 풍경을 찾아 나서 보세요."
|
||||||
|
],
|
||||||
|
"구름 조금": [
|
||||||
|
"구름이 조금 낀 날입니다. 볕이 강하지 않아 걷기에 오히려 좋습니다.",
|
||||||
|
"하늘에 구름이 떠 있지만 나들이엔 무리가 없습니다.",
|
||||||
|
"구름 사이로 비치는 햇살을 즐기며 산책해 보세요.",
|
||||||
|
"그늘이 반가운 날, 야외 일정을 여유롭게 잡아 보세요.",
|
||||||
|
"가벼운 겉옷 하나면 하루 종일 다니기 좋겠습니다."
|
||||||
],
|
],
|
||||||
"흐림": [
|
"흐림": [
|
||||||
"흐린 날에는 실내에서 느긋하게 쉬어 가도 좋겠습니다.",
|
"흐린 날에는 실내에서 느긋하게 쉬어 가도 좋겠습니다.",
|
||||||
@ -21,20 +28,6 @@
|
|||||||
"천천히 쉬면서 돌아갈 때 가져갈 추억을 골라 보세요.",
|
"천천히 쉬면서 돌아갈 때 가져갈 추억을 골라 보세요.",
|
||||||
"분주한 일정 사이에 조용한 휴식을 끼워 넣어 보세요."
|
"분주한 일정 사이에 조용한 휴식을 끼워 넣어 보세요."
|
||||||
],
|
],
|
||||||
"비": [
|
|
||||||
"비가 오는 동안에는 실내에서 쉬며 일정을 조정해 보세요.",
|
|
||||||
"서두르지 말고 비가 잦아드는지 확인한 뒤 움직이세요.",
|
|
||||||
"빗소리를 들으며 읽고 싶었던 글을 펼쳐 보세요.",
|
|
||||||
"젖은 물건을 정리하고 잠깐 쉬어 가세요.",
|
|
||||||
"바깥 일정 대신 함께 찍은 사진을 나눠 봐도 좋겠습니다."
|
|
||||||
],
|
|
||||||
"눈": [
|
|
||||||
"눈 오는 날에는 실내에서 쉬며 바깥 상황을 살펴보세요.",
|
|
||||||
"다음 이동을 서두르지 말고 쉬어 가세요.",
|
|
||||||
"따뜻하게 머물며 오늘의 여행을 한 줄 남겨 보세요.",
|
|
||||||
"실내에서 다음 일정과 이동편을 확인해 보세요.",
|
|
||||||
"눈길을 나서기 전에는 여유 있게 준비하세요."
|
|
||||||
],
|
|
||||||
"안개": [
|
"안개": [
|
||||||
"안개가 낮게 깔린 아침입니다. 서두르지 말고 천천히 나서 보세요.",
|
"안개가 낮게 깔린 아침입니다. 서두르지 말고 천천히 나서 보세요.",
|
||||||
"시야가 흐린 날에는 가까운 곳부터 둘러보는 편이 좋습니다.",
|
"시야가 흐린 날에는 가까운 곳부터 둘러보는 편이 좋습니다.",
|
||||||
@ -42,26 +35,166 @@
|
|||||||
"운전하신다면 평소보다 여유 있게 출발하시길 권합니다.",
|
"운전하신다면 평소보다 여유 있게 출발하시길 권합니다.",
|
||||||
"뿌연 공기 너머로 보이는 풍경도 오늘만의 그림입니다."
|
"뿌연 공기 너머로 보이는 풍경도 오늘만의 그림입니다."
|
||||||
],
|
],
|
||||||
"이슬비": [
|
"착빙성 안개": [
|
||||||
|
"서리 안개가 낀 아침입니다. 노면이 미끄러울 수 있으니 조심하세요.",
|
||||||
|
"안개와 함께 기온이 낮아 도로가 얼어 있을 수 있습니다.",
|
||||||
|
"이른 시간 이동은 특히 주의하고 서두르지 마세요.",
|
||||||
|
"안개가 걷히고 기온이 오른 뒤 나서는 편이 안전합니다.",
|
||||||
|
"차량·보행 모두 시야와 노면을 함께 살펴보세요."
|
||||||
|
],
|
||||||
|
"가벼운 이슬비": [
|
||||||
"가는 비가 내립니다. 우산 하나면 산책에 무리가 없습니다.",
|
"가는 비가 내립니다. 우산 하나면 산책에 무리가 없습니다.",
|
||||||
"빗방울이 약해 걷기에는 오히려 조용하고 좋은 날입니다.",
|
"빗방울이 약해 걷기에는 오히려 조용하고 좋은 날입니다.",
|
||||||
"얇은 겉옷 하나 챙기시면 이슬비 정도는 걱정 없습니다.",
|
"얇은 겉옷 하나 챙기시면 이슬비 정도는 걱정 없습니다.",
|
||||||
"젖은 길이 미끄러울 수 있으니 편한 신발을 권합니다.",
|
"젖은 길이 미끄러울 수 있으니 편한 신발을 권합니다.",
|
||||||
"빗소리가 작게 깔리는 날, 실내에서 쉬어 가기도 좋습니다."
|
"빗소리가 작게 깔리는 날, 실내에서 쉬어 가기도 좋습니다."
|
||||||
],
|
],
|
||||||
"소나기": [
|
"보통 이슬비": [
|
||||||
|
"보통 세기의 이슬비가 내립니다. 우산 하나로 충분합니다.",
|
||||||
|
"옷이 젖을 수 있으니 우산이나 방수 겉옷을 챙기세요.",
|
||||||
|
"이슬비가 이어지는 동안은 실내 일정을 먼저 잡아 보세요.",
|
||||||
|
"그치는 틈을 봐서 짧게 이동하는 정도는 괜찮습니다.",
|
||||||
|
"빗소리를 들으며 잠시 여유를 가져도 좋겠습니다."
|
||||||
|
],
|
||||||
|
"강한 이슬비": [
|
||||||
|
"이슬비치고는 제법 굵게 내립니다. 우산을 단단히 챙기세요.",
|
||||||
|
"옷과 신발이 젖기 쉬우니 방수 채비를 해 두세요.",
|
||||||
|
"바깥 일정은 짧게 잡고 실내에서 기다리는 편이 낫겠습니다.",
|
||||||
|
"빗줄기가 가늘어도 오래 맞으면 금방 젖으니 주의하세요.",
|
||||||
|
"그칠 때까지는 무리해서 이동하지 않는 게 좋겠습니다."
|
||||||
|
],
|
||||||
|
"가벼운 착빙성 이슬비": [
|
||||||
|
"가벼운 착빙성 이슬비입니다. 노면이 얼 수 있어 걸음을 조심하세요.",
|
||||||
|
"얼어붙은 길이 있을 수 있으니 평소보다 천천히 움직이세요.",
|
||||||
|
"차량 이동도 서행하고 급제동은 피하세요.",
|
||||||
|
"실내에서 잠시 기다렸다 나서는 편을 권합니다.",
|
||||||
|
"그늘지거나 다리 위 같은 곳은 특히 미끄러우니 유의하세요."
|
||||||
|
],
|
||||||
|
"강한 착빙성 이슬비": [
|
||||||
|
"강한 착빙성 이슬비가 내립니다. 바깥 이동은 최대한 미루세요.",
|
||||||
|
"노면 결빙 위험이 크니 실내에 머무르는 편이 안전합니다.",
|
||||||
|
"부득이 이동한다면 미끄럼 방지 신발을 챙기세요.",
|
||||||
|
"차량도 도로 상태를 확인한 뒤 서행으로 움직이세요.",
|
||||||
|
"기상특보를 확인하고 해빙될 때까지 기다려 보세요."
|
||||||
|
],
|
||||||
|
"약한 비": [
|
||||||
|
"약한 비가 내립니다. 우산 하나면 짧은 이동은 무리 없습니다.",
|
||||||
|
"가볍게 챙기고 나서도 크게 불편하지 않겠습니다.",
|
||||||
|
"비가 잦아드는 시간을 골라 움직여 보세요.",
|
||||||
|
"젖어도 금방 마를 정도니 편하게 다녀오세요.",
|
||||||
|
"우산을 챙기고 평소처럼 일정을 이어가도 좋겠습니다."
|
||||||
|
],
|
||||||
|
"보통 비": [
|
||||||
|
"비가 오는 동안에는 실내에서 쉬며 일정을 조정해 보세요.",
|
||||||
|
"서두르지 말고 비가 잦아드는지 확인한 뒤 움직이세요.",
|
||||||
|
"빗소리를 들으며 읽고 싶었던 글을 펼쳐 보세요.",
|
||||||
|
"젖은 물건을 정리하고 잠깐 쉬어 가세요.",
|
||||||
|
"바깥 일정 대신 함께 찍은 사진을 나눠 봐도 좋겠습니다."
|
||||||
|
],
|
||||||
|
"강한 비": [
|
||||||
|
"빗줄기가 굵습니다. 이동을 미루고 실내에서 기다리세요.",
|
||||||
|
"강한 비가 그칠 때까지 바깥 일정은 잠시 접어 두세요.",
|
||||||
|
"물웅덩이·배수 상황을 살피고 이동 경로를 다시 확인하세요.",
|
||||||
|
"우산만으로는 부족할 수 있으니 방수 옷차림을 챙기세요.",
|
||||||
|
"굵은 빗속 이동은 삼가고 안전한 곳에서 쉬어 가세요."
|
||||||
|
],
|
||||||
|
"약한 착빙성 비": [
|
||||||
|
"약한 착빙성 비입니다. 노면이 얼기 시작할 수 있어 조심하세요.",
|
||||||
|
"걸음을 천천히 옮기고 급한 이동은 피하세요.",
|
||||||
|
"차량은 서행하고 앞뒤 거리를 넉넉히 두세요.",
|
||||||
|
"가능하면 이동을 미루고 실내에서 기다려 보세요.",
|
||||||
|
"다리·그늘진 도로는 특히 미끄러우니 유의하세요."
|
||||||
|
],
|
||||||
|
"강한 착빙성 비": [
|
||||||
|
"강한 착빙성 비가 내립니다. 바깥 이동을 삼가고 실내에 머무르세요.",
|
||||||
|
"도로가 얼어붙을 수 있으니 되도록 나서지 마세요.",
|
||||||
|
"부득이 이동해야 한다면 걸음을 평소보다 훨씬 천천히 옮기세요.",
|
||||||
|
"차량 이동도 미끄러질 수 있으니 서두르지 마세요.",
|
||||||
|
"기상특보를 확인하고 해빙될 때까지 일정을 미뤄 두세요."
|
||||||
|
],
|
||||||
|
"약한 눈": [
|
||||||
|
"약한 눈이 흩날립니다. 짧은 나들이 정도는 다녀올 만합니다.",
|
||||||
|
"쌓이기 전이라면 평소처럼 움직여도 괜찮겠습니다.",
|
||||||
|
"따뜻하게 챙기고 잠깐의 눈길 산책을 즐겨 보세요.",
|
||||||
|
"노면이 젖어 있을 수 있으니 걸음만 조심하세요.",
|
||||||
|
"눈이 그치는 틈을 봐서 이동해도 좋겠습니다."
|
||||||
|
],
|
||||||
|
"보통 눈": [
|
||||||
|
"눈 오는 날에는 실내에서 쉬며 바깥 상황을 살펴보세요.",
|
||||||
|
"다음 이동을 서두르지 말고 쉬어 가세요.",
|
||||||
|
"따뜻하게 머물며 오늘의 여행을 한 줄 남겨 보세요.",
|
||||||
|
"실내에서 다음 일정과 이동편을 확인해 보세요.",
|
||||||
|
"눈길을 나서기 전에는 여유 있게 준비하세요."
|
||||||
|
],
|
||||||
|
"강한 눈": [
|
||||||
|
"눈이 강하게 쌓입니다. 바깥 이동은 미루는 편이 좋습니다.",
|
||||||
|
"제설이 될 때까지 실내에서 기다려 보세요.",
|
||||||
|
"이동해야 한다면 미끄럼 방지 신발과 여유 시간을 챙기세요.",
|
||||||
|
"차량 이동은 도로 상황을 확인한 뒤 움직이세요.",
|
||||||
|
"따뜻하게 머물며 눈이 그치기를 기다려 보세요."
|
||||||
|
],
|
||||||
|
"싸라기눈": [
|
||||||
|
"싸라기눈이 흩날립니다. 노면이 미끄러울 수 있어요.",
|
||||||
|
"짧은 이동이라도 걸음을 조심스럽게 옮기세요.",
|
||||||
|
"우산보다는 모자나 방한용품이 도움이 되겠습니다.",
|
||||||
|
"그치는 틈을 봐서 다음 일정을 잡아 보세요.",
|
||||||
|
"쌓이지 않아도 걸을 때는 발밑을 살펴보세요."
|
||||||
|
],
|
||||||
|
"약한 소나기": [
|
||||||
|
"짧게 스치는 소나기입니다. 잠깐 처마 밑에서 기다려도 됩니다.",
|
||||||
|
"갑자기 내릴 수 있으니 우산을 챙겨 두면 든든합니다.",
|
||||||
|
"소나기가 그치면 이어서 일정을 다니셔도 좋겠습니다.",
|
||||||
|
"젖은 길만 조심하면 크게 무리 없는 날씨입니다.",
|
||||||
|
"짧은 비를 피할 실내 공간을 미리 봐 두세요."
|
||||||
|
],
|
||||||
|
"보통 소나기": [
|
||||||
"소나기가 지나가는 중입니다. 잠시 기다리면 금세 갭니다.",
|
"소나기가 지나가는 중입니다. 잠시 기다리면 금세 갭니다.",
|
||||||
"갑자기 쏟아질 수 있으니 우산을 꼭 챙겨 나가세요.",
|
"갑자기 쏟아질 수 있으니 우산을 꼭 챙겨 나가세요.",
|
||||||
"비가 그친 뒤 공기가 가장 맑습니다. 그때를 노려 보세요.",
|
"비가 그친 뒤 공기가 가장 맑습니다. 그때를 노려 보세요.",
|
||||||
"지금은 실내에 머물다가 하늘이 열리면 나서는 편이 좋습니다.",
|
"지금은 실내에 머물다가 하늘이 열리면 나서는 편이 좋습니다.",
|
||||||
"짧고 굵게 지나가는 비입니다. 일정을 통째로 바꾸실 필요는 없습니다."
|
"짧고 굵게 지나가는 비입니다. 일정을 통째로 바꾸실 필요는 없습니다."
|
||||||
],
|
],
|
||||||
|
"강한 소나기": [
|
||||||
|
"갑자기 쏟아지는 강한 소나기입니다. 이동을 잠시 멈추세요.",
|
||||||
|
"그칠 때까지 안전한 실내에서 기다리는 편이 좋습니다.",
|
||||||
|
"젖은 도로가 미끄러울 수 있으니 이동 시 주의하세요.",
|
||||||
|
"우산만으로는 부족할 수 있어 방수 옷차림을 챙기세요.",
|
||||||
|
"소나기가 지나간 뒤 도로 상황을 확인하고 움직이세요."
|
||||||
|
],
|
||||||
|
"약한 소나기눈": [
|
||||||
|
"짧게 흩날리는 눈입니다. 잠깐 피했다 이동해도 됩니다.",
|
||||||
|
"노면이 젖거나 살짝 얼 수 있어 걸음을 조심하세요.",
|
||||||
|
"따뜻하게 챙기고 짧은 외출 정도는 다녀올 만합니다.",
|
||||||
|
"그치는 틈을 봐서 다음 장소로 이동해 보세요.",
|
||||||
|
"쌓이기 전이라면 크게 무리는 없겠습니다."
|
||||||
|
],
|
||||||
|
"강한 소나기눈": [
|
||||||
|
"갑자기 쏟아지는 눈입니다. 이동을 잠시 멈추고 기다리세요.",
|
||||||
|
"노면이 급격히 미끄러워질 수 있어 주의가 필요합니다.",
|
||||||
|
"실내에서 그치기를 기다렸다 움직이는 편이 안전합니다.",
|
||||||
|
"차량 이동은 서행하고 앞차와 거리를 넉넉히 두세요.",
|
||||||
|
"쌓인 눈이 있다면 이동 전 노면 상태부터 확인하세요."
|
||||||
|
],
|
||||||
"뇌우": [
|
"뇌우": [
|
||||||
"천둥번개가 치고 있습니다. 실외 활동은 잠시 미뤄 주세요.",
|
"천둥번개가 치고 있습니다. 실외 활동은 잠시 미뤄 주세요.",
|
||||||
"비바람이 강합니다. 오늘은 실내에서 쉬어 가시길 권합니다.",
|
"비바람이 강합니다. 오늘은 실내에서 쉬어 가시길 권합니다.",
|
||||||
"번개가 잦을 때는 높은 곳과 물가를 피하는 편이 안전합니다.",
|
"번개가 잦을 때는 높은 곳과 물가를 피하는 편이 안전합니다.",
|
||||||
"날이 거칩니다. 이동 계획이 있다면 시간을 넉넉히 잡으세요.",
|
"날이 거칩니다. 이동 계획이 있다면 시간을 넉넉히 잡으세요.",
|
||||||
"궂은 날씨가 지나가면 하늘이 크게 열립니다. 조금만 기다려 보세요."
|
"궂은 날씨가 지나가면 하늘이 크게 열립니다. 조금만 기다려 보세요."
|
||||||
|
],
|
||||||
|
"약한 우박 뇌우": [
|
||||||
|
"천둥·번개와 함께 약한 우박이 내릴 수 있습니다. 실내에 머무르세요.",
|
||||||
|
"우박이 그칠 때까지 차양 아래보다는 건물 안이 안전합니다.",
|
||||||
|
"야외 물건이 있다면 잠시 안으로 들여 두세요.",
|
||||||
|
"번개가 잦아든 뒤 이동 여부를 다시 확인하세요.",
|
||||||
|
"차량도 우박 피해가 없는 곳에 세워 두는 편이 좋습니다."
|
||||||
|
],
|
||||||
|
"강한 우박 뇌우": [
|
||||||
|
"굵은 우박을 동반한 뇌우입니다. 반드시 실내에 머무르세요.",
|
||||||
|
"창가나 개방된 곳을 피하고 안전한 공간에서 기다리세요.",
|
||||||
|
"차량은 우박을 피할 수 있는 곳에 세워 두세요.",
|
||||||
|
"기상특보를 확인하고 뇌우가 완전히 지나간 뒤 나서세요.",
|
||||||
|
"야외 활동은 오늘 일정에서 빼는 편이 안전하겠습니다."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"temperature": {
|
"temperature": {
|
||||||
|
|||||||
@ -1,28 +1,44 @@
|
|||||||
import {expect, it} from 'vitest';
|
import {expect, it} from 'vitest';
|
||||||
import {condition} from './use-live-weather';
|
import {condition} from './use-live-weather';
|
||||||
|
|
||||||
it('recognizes fog, showers and thunderstorms as distinct conditions', () => {
|
const WEATHER_CODE_LABELS: Record<number, string> = {
|
||||||
expect(condition(45)).toBe('안개');
|
0: '맑음',
|
||||||
expect(condition(48)).toBe('안개');
|
1: '대체로 맑음',
|
||||||
expect(condition(80)).toBe('소나기');
|
2: '구름 조금',
|
||||||
expect(condition(81)).toBe('소나기');
|
3: '흐림',
|
||||||
expect(condition(82)).toBe('소나기');
|
45: '안개',
|
||||||
expect(condition(85)).toBe('소나기');
|
48: '착빙성 안개',
|
||||||
expect(condition(86)).toBe('소나기');
|
51: '가벼운 이슬비',
|
||||||
expect(condition(95)).toBe('뇌우');
|
53: '보통 이슬비',
|
||||||
expect(condition(96)).toBe('뇌우');
|
55: '강한 이슬비',
|
||||||
expect(condition(99)).toBe('뇌우');
|
56: '가벼운 착빙성 이슬비',
|
||||||
|
57: '강한 착빙성 이슬비',
|
||||||
|
61: '약한 비',
|
||||||
|
63: '보통 비',
|
||||||
|
65: '강한 비',
|
||||||
|
66: '약한 착빙성 비',
|
||||||
|
67: '강한 착빙성 비',
|
||||||
|
71: '약한 눈',
|
||||||
|
73: '보통 눈',
|
||||||
|
75: '강한 눈',
|
||||||
|
77: '싸라기눈',
|
||||||
|
80: '약한 소나기',
|
||||||
|
81: '보통 소나기',
|
||||||
|
82: '강한 소나기',
|
||||||
|
85: '약한 소나기눈',
|
||||||
|
86: '강한 소나기눈',
|
||||||
|
95: '뇌우',
|
||||||
|
96: '약한 우박 뇌우',
|
||||||
|
99: '강한 우박 뇌우',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('gives every WMO weather code its own label', () => {
|
||||||
|
for (const [code, label] of Object.entries(WEATHER_CODE_LABELS)) {
|
||||||
|
expect(condition(Number(code))).toBe(label);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('splits rain into drizzle, normal, heavy and freezing', () => {
|
it('falls back to overcast for unknown codes', () => {
|
||||||
expect(condition(51)).toBe('이슬비');
|
expect(condition(4)).toBe('흐림');
|
||||||
expect(condition(53)).toBe('이슬비');
|
expect(condition(-1)).toBe('흐림');
|
||||||
expect(condition(55)).toBe('이슬비');
|
|
||||||
expect(condition(61)).toBe('비');
|
|
||||||
expect(condition(63)).toBe('비');
|
|
||||||
expect(condition(65)).toBe('강한비');
|
|
||||||
expect(condition(56)).toBe('어는비');
|
|
||||||
expect(condition(57)).toBe('어는비');
|
|
||||||
expect(condition(66)).toBe('어는비');
|
|
||||||
expect(condition(67)).toBe('어는비');
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,18 +5,43 @@ export interface LiveWeather extends WeatherSnapshot {
|
|||||||
stale?: boolean;
|
stale?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** WMO weather code → 한 줄. ★ solution/backend/services/site_payload.py 의 _weather_condition() 과 같은 구간이어야 한다. */
|
/**
|
||||||
|
* WMO weather code → 한 줄. 코드 하나마다 고유 문구다(뭉치지 않는다) — ★
|
||||||
|
* solution/backend/services/site_payload.py 의 _WEATHER_CONDITION_BY_CODE 와 같은 표여야 한다.
|
||||||
|
*/
|
||||||
|
const WEATHER_CONDITION_BY_CODE: Record<number, string> = {
|
||||||
|
0: '맑음',
|
||||||
|
1: '대체로 맑음',
|
||||||
|
2: '구름 조금',
|
||||||
|
3: '흐림',
|
||||||
|
45: '안개',
|
||||||
|
48: '착빙성 안개',
|
||||||
|
51: '가벼운 이슬비',
|
||||||
|
53: '보통 이슬비',
|
||||||
|
55: '강한 이슬비',
|
||||||
|
56: '가벼운 착빙성 이슬비',
|
||||||
|
57: '강한 착빙성 이슬비',
|
||||||
|
61: '약한 비',
|
||||||
|
63: '보통 비',
|
||||||
|
65: '강한 비',
|
||||||
|
66: '약한 착빙성 비',
|
||||||
|
67: '강한 착빙성 비',
|
||||||
|
71: '약한 눈',
|
||||||
|
73: '보통 눈',
|
||||||
|
75: '강한 눈',
|
||||||
|
77: '싸라기눈',
|
||||||
|
80: '약한 소나기',
|
||||||
|
81: '보통 소나기',
|
||||||
|
82: '강한 소나기',
|
||||||
|
85: '약한 소나기눈',
|
||||||
|
86: '강한 소나기눈',
|
||||||
|
95: '뇌우',
|
||||||
|
96: '약한 우박 뇌우',
|
||||||
|
99: '강한 우박 뇌우',
|
||||||
|
};
|
||||||
|
|
||||||
export function condition(code: number): string {
|
export function condition(code: number): string {
|
||||||
if (code === 0) return '맑음';
|
return WEATHER_CONDITION_BY_CODE[code] ?? '흐림';
|
||||||
if (code >= 1 && code <= 2) return '구름많음';
|
|
||||||
if (code === 3) return '흐림';
|
|
||||||
if (code === 45 || code === 48) return '안개';
|
|
||||||
if (code >= 51 && code <= 57) return '이슬비';
|
|
||||||
if (code >= 61 && code <= 67) return '비';
|
|
||||||
if ((code >= 71 && code <= 77) || code === 85 || code === 86) return '눈';
|
|
||||||
if (code >= 80 && code <= 82) return '소나기';
|
|
||||||
if (code >= 95 && code <= 99) return '뇌우';
|
|
||||||
return '흐림';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 프리렌더 스냅샷으로 시작하고, 하이드레이션이 끝난 뒤에만 최신 캐시를 반영한다. */
|
/** 프리렌더 스냅샷으로 시작하고, 하이드레이션이 끝난 뒤에만 최신 캐시를 반영한다. */
|
||||||
|
|||||||
@ -12,20 +12,20 @@ function render(weather?: WeatherSnapshot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
it('uses the first sky and temperature lines for deterministic HTML', () => {
|
it('uses the first sky and temperature lines for deterministic HTML', () => {
|
||||||
const weather = {temperature: 30, observedAt: '2026-09-15T12:00', condition: '구름많음', noteSets: {'구름많음': ['구름 첫 줄', '다음 줄']}, tempNoteSets: {'혹서': ['더위 첫 줄']}};
|
const weather = {temperature: 30, observedAt: '2026-09-15T12:00', condition: '대체로 맑음', noteSets: {'대체로 맑음': ['맑음 첫 줄', '다음 줄']}, tempNoteSets: {'혹서': ['더위 첫 줄']}};
|
||||||
expect(render(weather)).toContain('구름 첫 줄');
|
expect(render(weather)).toContain('맑음 첫 줄');
|
||||||
expect(render(weather)).toContain('더위 첫 줄');
|
expect(render(weather)).toContain('더위 첫 줄');
|
||||||
expect(render(weather)).not.toContain('다음 줄');
|
expect(render(weather)).not.toContain('다음 줄');
|
||||||
expect(render(weather)).toBe(render(weather));
|
expect(render(weather)).toBe(render(weather));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses condition-specific notes for fog, showers and thunderstorms', () => {
|
it('uses condition-specific notes for fog, showers and thunderstorms', () => {
|
||||||
const weather = {temperature: 20, observedAt: '2026-09-15T12:00', condition: '소나기', noteSets: {'소나기': ['소나기 첫 줄']}, tempNoteSets: {'선선': ['선선 첫 줄']}};
|
const weather = {temperature: 20, observedAt: '2026-09-15T12:00', condition: '보통 소나기', noteSets: {'보통 소나기': ['소나기 첫 줄']}, tempNoteSets: {'선선': ['선선 첫 줄']}};
|
||||||
expect(render(weather)).toContain('소나기 첫 줄');
|
expect(render(weather)).toContain('소나기 첫 줄');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses condition-specific notes for the rain subtypes', () => {
|
it('uses condition-specific notes for the rain subtypes', () => {
|
||||||
const weather = {temperature: 5, observedAt: '2026-09-15T12:00', condition: '어는비', noteSets: {'어는비': ['어는비 첫 줄'], '비': ['일반 비 첫 줄']}, tempNoteSets: {'추움': ['추움 첫 줄']}};
|
const weather = {temperature: 5, observedAt: '2026-09-15T12:00', condition: '강한 착빙성 비', noteSets: {'강한 착빙성 비': ['어는비 첫 줄'], '보통 비': ['일반 비 첫 줄']}, tempNoteSets: {'추움': ['추움 첫 줄']}};
|
||||||
expect(render(weather)).toContain('어는비 첫 줄');
|
expect(render(weather)).toContain('어는비 첫 줄');
|
||||||
expect(render(weather)).not.toContain('일반 비 첫 줄');
|
expect(render(weather)).not.toContain('일반 비 첫 줄');
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user