Tennis Playability Index (TMI) v2 — Specification
Status: Reference — Current Date: 2026-04-05 Code: src/domain/utils/playability.ts (canonical), fetch-weather/index.ts (Deno duplicate)
Formula (v2)
Veto gate: if any factor = 0 → score = 0 (safety-critical)
Otherwise: score = weighted_geometric_mean(
heat^0.25, ← Heat Index or KMA sensible temp (May-Sep)
cold^0.20, ← Raw temperature
rain^0.20, ← Precipitation probability + active rain
humidity^0.10, ← NEW: Dew point comfort
air^0.10, ← PM2.5 (환경부 thresholds)
wind^0.10, ← Wind speed
uv^0.05, ← NEW: UV Index (informational, no veto)
)Why Weighted Geometric Mean (not min())
v1 used min(all factors) — one bad factor vetoed the entire score. This was too harsh: wind at 35 km/h shouldn't make the score 30 when everything else is perfect.
v2 uses weighted geometric mean (same formula as UN HDI, OECD Better Life Index):
- Bad factors still pull the score down (more than arithmetic mean)
- But one moderate factor doesn't tank an otherwise great day
- Hard veto preserved at 0 (active rain, extreme heat) for safety
Source: Composite indicator methodology (Springer, 2017)
Why Piecewise Linear (not step functions)
v1 used hard steps: Heat Index 26°C = 100, 27°C = 80 (instant -20 jump). v2 interpolates linearly between breakpoints, producing smoother, more defensible transitions.
Thresholds (piecewise linear, peer-reviewed anchors)
Heat (체감온도 / Heat Index)
| Input (°C) | Score | Source |
|---|---|---|
| ≤26 | 100 | UTCI comfort zone |
| 30 | 80 | UTCI moderate heat stress |
| 32 | 60 | KOSHA mandatory cooling |
| 34 | 30 | 폭염주의보, KOSHA mandatory rest |
| ≥35 | 0 | 폭염경보, ACSM Black Flag |
Heat source priority:
- KMA sensible temp (
getSenTaIdxV4) — May-Sep only, includes solar radiation - NWS Heat Index (Rothfusz regression) — fallback, temp + humidity only
Cold
| Input (°C) | Score | Source |
|---|---|---|
| ≤-10 | 0 | Extreme cold |
| -5 | 10 | Injury risk, ball hardness |
| 0 | 40 | UTCI moderate cold stress |
| 5 | 70 | UTCI slight cold stress |
| ≥10 | 100 | Comfortable |
Air Quality (PM2.5 µg/m³)
| Input | Score | Source |
|---|---|---|
| ≤15 | 100 | 환경부 "좋음" |
| 35 | 80 | 환경부 "보통" |
| 75 | 30 | 환경부 "나쁨" |
| ≥100 | 0 | 환경부 "매우나쁨" |
Wind (km/h)
| Input | Score | Source |
|---|---|---|
| ≤15 | 100 | No effect on ball |
| 30 | 70 | Noticeable drift |
| 50 | 30 | Ball trajectory seriously affected |
| ≥65 | 0 | Impractical |
Rain
| Condition | Score |
|---|---|
| Active rain (PTY > 0) | 0 (hard veto — wet court unsafe) |
| POP 0% | 100 |
| POP 30% | 85 |
| POP 60% | 40 |
| POP 80% | 15 |
| POP 100% | 0 |
Humidity (Dew Point °C) — NEW in v2
Captures "muggy but not hot" discomfort that Heat Index misses at moderate temps.
| Dew Point | Score | Source |
|---|---|---|
| ≤13 | 100 | Dry/comfortable (NWS) |
| 16 | 90 | Slightly humid |
| 18 | 70 | Humid |
| 21 | 50 | Very humid (oppressive) |
| 24 | 20 | Extremely muggy |
| ≥27 | 0 | Tropical, unsafe for sport |
Dew point is a better comfort metric than RH%: 50% humidity feels different at 20°C vs 35°C, but dew point 20°C always feels the same. Source: NWS: Dew Point vs Humidity
UV Index — NEW in v2
Informational factor with low weight (0.05). No hard veto — UV doesn't make tennis unplayable but affects long-term health.
| UV Index | Score | Level |
|---|---|---|
| 0-2 | 100 | 낮음 (Low) |
| 3-5 | 90 | 보통 (Moderate) |
| 6-7 | 70 | 높음 (High) |
| 8-10 | 50 | 매우높음 (Very High) |
| ≥11 | 30 | 위험 (Extreme) |
Source: Tennis players receive median 7.5 SED/day UV exposure — Photoprotection in Outdoor Sports (PMC)
Display (5 levels)
| Score | Level | Korean | Color | Tip |
|---|---|---|---|---|
| 80-100 | Excellent | 최적 | #22C55E | 테니스 하기 딱 좋아요 |
| 60-79 | Good | 좋음 | #3B82F6 | 충분한 수분 섭취 권장 |
| 40-59 | Fair | 보통 | #EAB308 | 주의하며 플레이하세요 |
| 20-39 | Poor | 나쁨 | #F97316 | 실내 코트를 추천해요 |
| 0-19 | Unplayable | 불가 | #EF4444 | 오늘은 쉬는 게 좋겠어요 |
Additional Display Data
| Data | Where Shown | Source |
|---|---|---|
| Estimated WBGT | WeatherDetailSheet | Computed from temp + humidity |
| Dew point | WeatherDetailSheet | Magnus-Tetens from temp + humidity |
| UV Index | WeatherDetailSheet | KMA 생활기상지수 getUVIdxV4 |
| ATP heat reference | WeatherDetailSheet (WBGT > 28°C) | ATP 2026 extreme heat rule |
Data Sources
| Input | API | Status |
|---|---|---|
| Temperature (TMP) | 기상청 단기예보 | Active |
| Humidity (REH) | 기상청 단기예보 | Active |
| Wind speed (WSD) | 기상청 단기예보 | Active |
| Precipitation (POP, PTY) | 기상청 단기예보 | Active |
| PM2.5 / PM10 | 에어코리아 | Active |
| UV Index | 기상청 생활기상지수 getUVIdxV4 | Active (deployed) |
| Sensible Temperature | 기상청 생활기상지수 getSenTaIdxV4 | Active (May-Sep only) |
| Medium-range forecast | 기상청 중기예보 | Active |
| Weather warnings | 기상청 기상특보 getPwnStatus | Active |
References
- ACSM Heat Guidelines — Black Flag at WBGT 28°C+
- ATP 2026 Extreme Heat Rule — WBGT 30.1°C breaks, 32.2°C suspension
- KOSHA — Mandatory cooling at 31°C, rest at 33°C
- 환경부 — PM2.5 air quality grades
- NWS — Heat Index (Rothfusz regression), Dew point vs humidity
- UTCI — Comfort zone boundaries (26°C upper, 9°C lower)
- UN HDI — Weighted geometric mean methodology for composite indices