Skip to content

Commit f7bad3b

Browse files
committed
러닝 지표 알고리즘 수정
1 parent 99de89a commit f7bad3b

2 files changed

Lines changed: 41 additions & 14 deletions

File tree

backend/src/domain/useCases/getRunningIndex.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,19 +134,14 @@ function calculateScore(m: AirQualityMetrics, w?: WeatherInfo, hour?: number): n
134134
function applyExtremeCap(score: number, m: AirQualityMetrics, w?: WeatherInfo, hour?: number): number {
135135
let cap = 100
136136

137-
// 미세먼지 — 매우 나쁨이면 bad, 나쁨이면 caution
137+
// 미세먼지 — "나쁨" 구간 내에서도 "매우나쁨" 경계에 가까울수록 cap을 선형으로 낮춘다.
138+
// 계단식(81~150 전부 cap 55) 방식은 경계값(예: PM10=139)을 과소평가하는 문제가 있어
139+
// 구간 내에서도 부드럽게 35까지 내려가도록 보간한다.
138140
// (pm25/pm10 null은 getRunningIndex에서 'unknown'으로 걸러지므로 여기서는 number 보장)
139-
if (m.pm25 != null) {
140-
if (m.pm25 > 75) cap = Math.min(cap, 35)
141-
else if (m.pm25 > 35) cap = Math.min(cap, 55)
142-
}
143-
144-
if (m.pm10 != null) {
145-
if (m.pm10 > 150) cap = Math.min(cap, 35)
146-
else if (m.pm10 > 80) cap = Math.min(cap, 55)
147-
}
141+
if (m.pm25 != null) cap = Math.min(cap, pm25Cap(m.pm25))
142+
if (m.pm10 != null) cap = Math.min(cap, pm10Cap(m.pm10))
148143

149-
if (!w) return Math.min(score, cap)
144+
if (!w) return Math.round(Math.min(score, cap))
150145

151146
// 자외선 — 낮 시간대에만 (야간에는 물리적으로 0)
152147
const isDaytime = hour === undefined || (hour >= 6 && hour < 20)
@@ -162,7 +157,35 @@ function applyExtremeCap(score: number, m: AirQualityMetrics, w?: WeatherInfo, h
162157
// 극한 기온 — 영하 또는 35°C 이상
163158
if (w.temperature <= 0 || w.temperature >= 35) cap = Math.min(cap, 30)
164159

165-
return Math.min(score, cap)
160+
return Math.round(Math.min(score, cap))
161+
}
162+
163+
/**
164+
* PM2.5 값에 따른 최종 점수 상한.
165+
* 35(나쁨 시작)에서 55, 75(매우나쁨 경계)에서 35로 선형 보간 후 이후는 35 고정.
166+
* PM2.5 = 36 → cap 55 (caution 상단)
167+
* PM2.5 = 55 → cap 45 (caution 하단)
168+
* PM2.5 = 75 → cap 35 (bad 진입)
169+
* PM2.5 > 75 → cap 35
170+
*/
171+
function pm25Cap(v: number): number {
172+
if (v <= 35) return 100
173+
if (v >= 75) return 35
174+
return 55 - ((v - 35) / 40) * 20
175+
}
176+
177+
/**
178+
* PM10 값에 따른 최종 점수 상한.
179+
* 80(나쁨 시작)에서 55, 150(매우나쁨 경계)에서 35로 선형 보간 후 이후는 35 고정.
180+
* PM10 = 81 → cap 55 (caution 상단)
181+
* PM10 = 139 → cap 38 (bad)
182+
* PM10 = 150 → cap 35 (bad)
183+
* PM10 > 150 → cap 35
184+
*/
185+
function pm10Cap(v: number): number {
186+
if (v <= 80) return 100
187+
if (v >= 150) return 35
188+
return 55 - ((v - 80) / 70) * 20
166189
}
167190

168191
/**

running-index-algorithm.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,16 +240,20 @@ cap = 100
240240

241241
| 항목 | 조건 | 상한 | 최대 상태 |
242242
|---|---|---|---|
243+
| 초미세먼지 (PM2.5) | 35~75 μg/m³ (나쁨 구간) | `55 - (v - 35) / 40 × 20` (55 → 35 선형) | caution~bad |
243244
| 초미세먼지 (PM2.5) | > 75 μg/m³ (매우 나쁨) | 35 | bad |
244-
| 초미세먼지 (PM2.5) | > 35 μg/m³ (나쁨) | 55 | caution |
245+
| 미세먼지 (PM10) | 80~150 μg/m³ (나쁨 구간) | `55 - (v - 80) / 70 × 20` (55 → 35 선형) | caution~bad |
245246
| 미세먼지 (PM10) | > 150 μg/m³ (매우 나쁨) | 35 | bad |
246-
| 미세먼지 (PM10) | > 80 μg/m³ (나쁨) | 55 | caution |
247247
| 자외선 (UV) | ≥ 11 (위험), 주간(06~20시) | 35 | bad |
248248
| 자외선 (UV) | 8~10 (매우높음), 주간(06~20시) | 55 | caution |
249249
| 강수 | 눈 / 진눈깨비 | 30 | bad |
250250
| 강수 || 45 | caution |
251251
| 기온 | ≤ 0°C 또는 ≥ 35°C | 30 | bad |
252252

253+
> PM2.5·PM10은 "나쁨"과 "매우나쁨" 사이를 계단식이 아니라 **선형으로 보간**합니다.
254+
> 예: PM10=81 → cap 55(caution), PM10=125 → cap 42(caution), PM10=139 → cap 38(bad), PM10=150 → cap 35(bad).
255+
> 이는 "매우나쁨" 경계 직전(예: 139μg/m³)의 체감 위험을 더 정확히 반영하기 위함입니다.
256+
253257
- **야간 UV 예외:** `hour`가 20~06시이면 UV 상한은 적용하지 않습니다(물리적으로 0).
254258
- **강수·기온 상한은** 기존 강제 감점과 함께 작동합니다. 강제 감점으로 이미 점수가 낮아졌다면 상한은 무의미하고, 다른 조건이 매우 좋아 점수가 높게 산출됐을 때 상한이 동작합니다.
255259
- **기상 데이터가 없어도** PM2.5·PM10 상한은 반드시 적용됩니다. 기상청 API 타임아웃/장애 상황에서 대기질만으로 점수를 계산하는 경로에서도 `applyExtremeCap`을 거치므로, PM10 > 150일 때 절대 '달리기 좋음' 이상이 나올 수 없습니다.

0 commit comments

Comments
 (0)