Skip to content

RPE 입력·세트 스코어 계약

이슈 #1397의 단순안으로 세트 스코어 계산을 개정한다. RPE 입력과 기존 추정 1RM 정책은 유지한다. 아래 규칙이 #885·#1164·#1241의 과거 점수 계산/분모 갱신 규칙을 대체한다.

1. 계산식

n은 이 세트에서 실제 완료한 반복수다. p(n)은 기존 Nuzzo 일반 곡선의 중심 비율이다. SQL의 strength_estimation_load_percent_v1(n, 'center')는 백분율이므로 100으로 나눈다. p(1)=1, p(5)=0.89864865, p(8)=0.83384615. 기존 표의 지원 범위는 1~20회이며 중량 세트가 이를 벗어나면 점수를 내지 않는다.

text
실측 rRM으로 기준 만들기: 기준 1RM = 실측 유효 중량 / p(r)
실측 1RM으로 기준 만들기: 기준 1RM = 실측 유효 중량
현재 세트의 기준 nRM = 기준 1RM × p(n)
중량 세트 스코어 = 실제 유효 중량 / 기준 nRM × 10
반복 세트 스코어 = 실제 완료 횟수 / 기준 최대 반복수 × 10

예: 실측 5RM 100kg이 기준이면 80kg × 5회의 점수는 8.0이다. 현재 세트 RPE가 6·8·10·미입력 중 어느 것이든 점수는 8.0이다. 반복수 기준이 10회라면 8회 세트는 8.0이다. 현재 RIR을 분자에 더하지 않는다.

유효 중량은 기존 계약 그대로 추가 무게 × 부하 배수 + 세션 체중 몫 − 보조 무게다. 직접 입력한 1RM은 추가 무게 프레임이므로 해당 세션의 배수와 체중 몫으로 변환한다. 추가 무게 0·보조 없음인 세트는 기존 최대 반복수 기록 가능 종목에서만 반복수판으로 센다.

2. 기준 하나 선택하기

  1. 현재 세션보다 앞선 완료 세션과 직접 입력 기록에서 가장 최근의 실측 하나를 고른다. 한계까지 완료한 세트(RPE 10 또는 1회 이상 성공 뒤 실패)는 실측 rRM/최대 반복수 후보다. 일반적인 1회 성공을 자동으로 최대 노력이라고 간주하지 않는다.
  2. 실측이 하나도 없으면 가장 최근의 유효한 추정값 하나를 임시 기준으로 쓴다. 기존 세트 추정 1RM 또는 추정 최대 반복수를 그대로 이용하며, 반복수에 추정 대표값이 없으면 기존 하한을 쓴다. 화면에는 점수 앞에 ‘약’을 붙인다.
  3. 둘 다 없으면 점수를 내지 않는다. 첫 세션을 자기 결과로 나누어 10점으로 만들지 않는다.

최근 순서는 운동 날짜 → 시작 시각(없으면 생성 시각) → 세션 ID → 세부 종목 순서 → 세트 순서 → 세트 ID다. 직접 입력의 날짜가 없으면 기존 초기 기준 기록처럼 가장 이전으로 취급한다. 날짜가 있는 직접 입력은 해당 날짜 이후 세션에 반영하고, 같은 날에는 입력 생성 시각보다 늦게 시작한 세션에 반영한다. 같은 시각의 직접 입력 간에는 생성 시각·ID로 하나를 정한다.

같은 세션의 모든 세트는 세션 전 기준만 쓴다. 그 세션의 실측은 다음 세션부터 반영된다. 새 실측이 낮으면 낮은 값으로 교체한다. 옛 PR은 원본으로 그대로 남는다. 실력 저하를 날짜만으로 추측해 기준을 자동 감쇠하지 않으며, 현재 실측이나 직접 입력 기록으로 갱신한다. 실측과 추정값을 혼합하거나 괴리에 따라 가중하지 않는다.

3. 표시와 서버 계약

  • 메인 세트만, 소수 1자리, 상한 없이 표시한다. 0회 실패·기준 없음·범위 밖 중량 세트는 ‘—’다.
  • 복합 세트는 점수가 있는 구성 동작의 반올림된 점수를 단순 평균하고 소수 1자리로 반올림한다. 구성 종목 중 어느 것으로 조회해도 같은 한 세트로 집계한다.
  • 서버 정본은 set_score_reference_v2 → set_score_session_v2다. 완료 답장, 상세 지표 맵, 기간/하루 리포트가 이를 공유한다. 통계 투영 완료 여부나 최신 상태 테이블에 따라 과거 세션 점수가 바뀌지 않는다.
  • wire 재료는 set_score_value(number|null), set_score_reference_kind(measured|estimated|null)다. 클라이언트는 단일 세트의 식을 다시 계산하지 않는다. 기존 재료만 있는 구형 캐시는 점수를 표시하지 않는다.
  • 기존 e1rm_representative_kg 등은 RPE를 반영하는 추정값이라는 원래 의미를 보존한다. recent_performed_1rm_kg / recent_performed_max_reps 응답 키에는 새 세션 전 기준을 담는다. 과거 user_exercise_set_score_states 및 관측 테이블의 동명 열은 호환용 투영이며 새 점수에서는 읽지 않는다.
  • 이 정책은 기록 대비 상대 수행량이다. 실제 체감 난이도나 피로의 측정값으로 해석하지 않는다. ‘약’은 현재 RPE 누락 여부가 아니라 기준이 추정값임을 뜻한다.

기존 기간·하루 집계는 배포 시 갱신 큐에 넣어 재계산한다. 원본 운동·PR 데이터의 DML은 없다. 수치 보존 리팩터링과 별개로 점수가 바뀌는 정책 개정이다.

4. RPE 입력 정본 (Phase 1 발효 — 2026-08-29, 20260829200000)

컬럼

  • exercise_set_part.perceived_rpe numeric(3,1)(구 exercise_sets, 이슈 #1215로 개명) — CHECK 1.0 ≤ x ≤ 10.0, NULL 허용(미입력).
  • 컬럼 주석으로 difficulty(레거시 봉인)와의 구분을 못박는다. 관측 투영 테이블(user_exercise_strength_observations 등)로의 전파는 Phase 3 설계 범위.

(퇴역) effort_level 유도 규칙 — 이슈 #1237로 삭제 (2026-09-04)

5단계 번호를 RPE에서 유도하던 규칙(clamp(floor(perceived_rpe) − 5, 1, 5))과 effort_scale_version = '1.0.0' 페어 계약은 컬럼 삭제와 함께 없어졌다. 서버·앱 어디에도 5단계 값이 없다. 옛 앱이 effort_level만 보내는 저장 요청은 한 릴리스 동안 perceived_rpe_from_set_payload_v1이 번호 + 5로 받는다(다음 릴리스에서 제거).

왕복 4층 (한 PR에서 동시 변경 — 이슈 #837 교훈)

지점변경
직렬화completedSessionSetProjectionperceivedRpe 방출 — 현재 rpeHalfValue가 소실되는 바로 그 지점
화이트리스트 2겹COMPLETED_SESSION_SET_WRITE_KEYS(camel DTO) + validateCompletedWorkoutPayload의 setKeys(snake payload)perceivedRpe / perceived_rpe 등재, 값 규칙 1.0~10.0
서버validate_session_payload_v5_core(세트 필드 화이트리스트) + save_session_v5_engineexercise_set_part insert(이슈 #1215, 2026-09-04 — 발효 당시는 validate_completed_workout_payload_v4_structure_core + v4 엔진의 exercise_sets insert)화이트리스트 등재 + perceived_rpe 기록 (엔진은 위치 기반 리더 — 컬럼·값 추출 둘 다 추가)
재진입persistedSetProjection 읽기 화이트리스트·매퍼snake 단일 키 perceived_rpe (dual-key 래칫 — camel 별칭 금지)
  • 표시: 세트 행의 @ RPE 값은 perceived_rpe 우선으로 전환. 표시 전용 임시 키 rpeHalfValue는 왕복 정본화와 함께 소멸.
  • 입력 UI(이슈 #1237): 숫자 입력칸(1.0~10.0, 소수 1자리)이 본체, 단축 버튼 6·7·8·9·10과 반값 6.5~9.5는 그 칸에 값을 넣는 지름길. 옛 difficulty 컬럼은 삭제됐다.
  • 검증: 7.5 저장 → 재진입 7.5 왕복 테스트, e1RM 추정·세트 목적 분류는 RPE 숫자에서 직접, 집계는 RPE 6칸(6 미만·6·7·8·9·10).