# 피로와 부하 — Fatigue / Training Load

> **단일 지표는 안 된다.** RPE/sRPE/Hooper/ACWR/velocity baseline — 다중 신호 복합으로 본다.
> 2023 ACWR 비판 논문도 "ACWR 단독" 사용을 까는 거지, multi-signal 자체는 표준이다.

## 측정 스케일 4단계

| 스케일 | 단위 | 측정 | Roomfit 구현 |
|---|---|---|---|
| Intra-set | rep-by-rep | velocity 감소 | ✅ VL% per set |
| Inter-set | set-by-set | velocity / volume / rest | ⚠️ rest_before_ms 미구현 |
| Inter-session | session 단위 | sRPE × duration, NM readiness | ✅ |
| Inter-day/week | daily/weekly | ACWR, monotony, Hooper | ✅ 계산기, ⚠️ MV refresh 미자동화 |

---

## 1. RPE — Rate of Perceived Exertion

**Zourdos 1-10 scale** (RIR-based):

| RPE | RIR | 의미 |
|---:|:---:|---|
| 10 | 0 | absolute failure |
| 9 | 1 | 1 rep 더 가능 |
| 8 | 2 | 2 rep 더 가능 |
| 7 | 3 | 3 rep 더 가능 |
| 6 | 4 | 4+ rep 가능 |
| 5 이하 | 5+ | very easy |

**관계**: `RPE = 10 - RIR`.

### 정확도 한계

- 경험자 > 초보자 (`r = -0.88 vs -0.77` velocity-RPE 상관)
- 낮은 %1RM, 많은 rep에서 정확도 ↓ (failure에서 멀어질수록)

→ **Roomfit 전략**: velocity-derived RIR이 주, 사용자 입력 RPE는 **교차검증용**.

### 저장

- `exercise_sets.rpe` (1-10), `exercise_sets.rir` (0-10)
- 입력 UI: `lib/features/workout/presentation/widgets/post_set_feedback_sheet.dart`
- 자동 페어: 한쪽 입력하면 다른 쪽 자동 제안 (Zourdos 매핑)

---

## 2. sRPE — Session RPE (Foster method)

**공식**:
```
sRPE_load (AU) = session_rpe (1-10) × duration_minutes
```
arbitrary unit (AU). 단순한 척도지만 weekly aggregate로 매우 robust.

### 활용 체인

```
sRPE_load → daily_load → ACWR → monotony → strain
```

### 저장

- `workout_sessions.session_rpe` (1-10)
- `workout_sessions.duration_min`
- `workout_sessions.srpe_load` — Postgres GENERATED 컬럼 (`session_rpe × duration_min`)
- 입력 UI: `lib/features/workout/presentation/widgets/session_feedback_sheet.dart`

### Dart 표현

```dart
final srpe = SrpeLoad(sessionRpe: 7, durationMinutes: 45);
print(srpe.arbitraryUnits); // 315
```

---

## 3. Hooper Index — Daily Wellness

**4문항 × 1-7 scale**:

| 항목 | 1 | 7 | 방향 |
|---|---|---|---|
| Sleep quality | very poor | excellent | 높을수록 ✓ |
| Fatigue | very low | very high | 낮을수록 ✓ |
| Soreness | very low | very high | 낮을수록 ✓ |
| Stress | very low | very high | 낮을수록 ✓ |

**Total** (4-28, 낮을수록 좋음):
```
hooper_total = fatigue + soreness + stress + (8 − sleep)
```

### 왜 Hooper

- HRV보다 민감하다는 2019 Buchheit 연구
- 웨어러블 없이 측정 가능 (소비자 앱 적합)
- 4문항 = 30초 안에 입력 가능

### 저장

- `daily_wellness` 테이블 (PK: `user_id, date`)
- `hooper_total`은 Postgres GENERATED 컬럼
- 입력 UI: `lib/features/wellness/presentation/widgets/hooper_entry_sheet.dart`
- 리포: `lib/features/wellness/data/wellness_repository.dart`

### Dart 표현

```dart
final hooper = HooperIndex(sleep: 5, fatigue: 3, soreness: 4, stress: 2);
print(hooper.total); // 12
```

---

## 4. ACWR — Acute:Chronic Workload Ratio

### EWMA 모델 (Williams-West 2017, 권장)

```
EWMA_today = load_today × λ + EWMA_yesterday × (1 − λ)

λ_acute   = 2 / (7 + 1)  = 0.25      // 7일 acute window
λ_chronic = 2 / (28 + 1) ≈ 0.0690    // 28일 chronic window

ACWR = EWMA_acute / EWMA_chronic
```

EWMA가 rolling average보다 민감 (최근 부하에 가중치 ↑).

### Risk zones

| ACWR | Zone | 해석 |
|---|---|---|
| < 0.8 | low (undertraining) | fitness 유지 안 됨 |
| 0.8 – 1.3 | **optimal (sweet spot)** | 적응 극대화 |
| 1.3 – 1.5 | caution | 주의 |
| 1.5 – 2.0 | high | 부상 위험 유의미 증가 |
| > 2.0 | very high | 부상 RR 6.5–21배 (running 데이터) |

### 입력

`daily_srpe_load` 시계열 (rest day = 0).

### 구현

```dart
final calc = AcwrCalculator();
for (final day in days) {
  calc.advanceDay(load: DailyLoad(day: day.date, srpeLoad: day.load));
}
print(calc.acwr);                          // 1.12
print(AcwrCalculator.classifyRisk(calc.acwr)); // AcwrRiskZone.optimal
```

`packages/roomfit_exercise/lib/src/load/acwr_calculator.dart`

### 저장

- 원천: `workout_sessions.srpe_load` (생성)
- Materialized view: `user_daily_load` — 일별 합계
- ACWR 자체는 **앱에서 계산** (DB에 영속 X). 필요 시 nightly Edge Function으로 derived 테이블 가능.

### 단독 지표 금지 (중요)

ACWR 하나로 결정 X. 같이 봐야 할 신호:
- Hooper total
- 첫 set MCV vs 7-day baseline (NM readiness)
- 사용자 입력 sRPE
- VL% 평균

---

## 5. Monotony & Strain (Foster)

### Monotony

```
monotony = mean(daily_load) / std(daily_load)   // 최근 7일
```

**해석**:
- < 1.5 — 건강한 변동성
- 1.5 – 2.0 — 주의
- > 2.0 — overtraining/illness 위험

### Strain

```
strain = weekly_load × monotony
```

높은 부하 + 단조로움 = stress 누적.

### 구현

`AcwrCalculator.monotony`, `.strain`. 마지막 7일 윈도우 자동 유지.

---

## 6. Neuromuscular Readiness

### 정의

당일 첫 warm-up MCV vs 사용자 7일 rolling baseline의 비율.

```
nm_readiness = warmup_mcv / baseline_mcv
```

### 분류

| Ratio | Zone | 해석 |
|---|---|---|
| ≥ 1.05 | supercompensated | 최근 deload 효과. PR 시도 가능 |
| 0.93 – 1.05 | fresh | 정상 |
| 0.85 – 0.93 | fatigued | 신경근 피로. volume/intensity 줄일 것 |
| < 0.85 | severely fatigued | deload / 휴식 권장 |

### 저장

- `workout_sessions.nm_readiness` (계산 결과)
- baseline은 앱에서 쿼리해서 계산 (`reps` 테이블 7일 평균)

### 구현

```dart
final nm = NeuromuscularReadiness.compute(
  warmupMcvMps: 0.95,
  baselineMcvMps: 1.00,
);
print(nm.ratio);          // 0.95
print(nm.readinessZone);  // NmReadinessZone.fresh
```

`packages/roomfit_exercise/lib/src/vbt/e1rm.dart`

---

## 7. Velocity Loss (intra-set 피로)

세트 내 rep-by-rep 피로 신호. 자세한 내용은 [vbt-metrics.md](vbt-metrics.md#8-velocity-loss).

요약:
- 첫 3개 rep MCV의 max를 baseline
- VL% = `(baseline − current) / baseline × 100`
- threshold 2회 연속 초과 → cutoff
- presets: `power 10% / strength 20% / hypertrophy 30%`

---

## 8. 의사결정 트리 — "이번 세션 어떻게 권할까"

```
세션 시작 시:
  Hooper(today) 입력 받음
  ↓
  warm-up 세트 진행 → first MCV 측정
  ↓
  baseline_mcv 쿼리 (지난 7일 평균)
  ↓
  NM Readiness 계산
  ↓
  ┌── nm_zone == severelyFatigued OR hooper > 22 → "deload 권장" UI
  ├── nm_zone == fatigued OR hooper > 18           → "강도 낮춤" 제안
  ├── ACWR > 1.5                                    → "오늘 가벼운 day로 전환?"
  └── 그 외                                          → 정상 진행
```

(현재 UI는 미구현. 데이터/계산 인프라만 준비됨.)

---

## 9. 데이터 흐름 요약

```
사용자 입력            앱 계산              영속
──────────────────    ──────────────────  ──────────────────
Hooper (1-7×4)    →   total (4-28)        daily_wellness
RPE/RIR per set   →   ─                    exercise_sets.rpe/rir
Session RPE       →   ─                    workout_sessions.session_rpe
Duration (auto)   →   srpe_load (gen)     workout_sessions.srpe_load
                       sRPE × duration

이미 저장된 데이터로 derived:
sessions.srpe_load 시계열 → daily_load (MV) → ACWR EWMA → risk zone (UI)
reps.mean_conc_velocity (warmup) → baseline 비교 → NM readiness
```

---

## 10. 외부 레퍼런스

- Foster 1998 — sRPE method
- Williams & West 2017 (PubMed 28003238) — EWMA ACWR
- Buchheit et al. 2019 — Hooper > HRV
- Gabbett 2016 — original ACWR injury risk
- 2020+ ACWR critique (Impellizzeri 2020) — single-metric 사용 비판
- Zourdos 2016 — RPE-RIR scale validation
- PMC9914552 (2024) — VL threshold meta-analysis

---

## 관련 코드

- 계산기: `packages/roomfit_exercise/lib/src/load/`
- 입력 UI:
  - `lib/features/workout/presentation/widgets/post_set_feedback_sheet.dart`
  - `lib/features/workout/presentation/widgets/session_feedback_sheet.dart`
  - `lib/features/wellness/presentation/widgets/hooper_entry_sheet.dart`
- 영속: `lib/features/workout/data/supabase_workout_repository.dart`, `lib/features/wellness/data/wellness_repository.dart`
