타 종목 1RM 기준 세트 % — 암묵 자기 종목 해석에서 기준 종목 영속·전 종목 선택까지 (2026-08-25)
- 기간: 2026-08-25 (세션 1, 오너 보고 원문: "70%는 각 종목의 무게가 아니라, 본인 clean and jerk 무게의 70%를 지칭하는 것임 … 우리 시스템에서는 타종목 1rm으로 세트의 %무게를 작성하는게 불가능함. 보완 필요" — 이슈 #676)
- 랜딩: PR #734(Phase 1~4,
325d5200) — 마이그레이션20260821750000Production 적용·check:remote-schema0 실패·Vercel 배포 확인(신규 마커load_percent_base_exercise_id메인 청크 실측) - 설계서: 없음(단일 세션 트랙) — Phase 계획서·예상 효과·개선사항 표는 이슈 #676 계획서 코멘트
- 정본:
planned_sets.load_percent_base_exercise_id(null=자기 종목),validated_entry_percent_base_v1, 스크럽 트리거scrub_planned_set_atomic_values_v1(%가 사라지면 기준 동반 소거), 클라 필드loadPctBase(세트)·pctBase(편집기 exercise) - 도구: 단독 마운트 스모크(레포 밖 scratchpad vite —
UiDesktopPlanEditor·UiWorkoutFlow(plan)픽스처 마운트, gitignored) - 게이트: pgTAP
planned_sets_load_percent_base.test.sql(12단언) · 왕복 하네스planRoundTripHarness% 기준 2건 ·plannedPercentBasePreservation.test.mjs(5건) ·desktopExerciseIdentity계약 재고정 - 버그리포트: 없음(기능 확장)
- 계약: 없음(신규 계약 문서 없이 스키마·코드 정본으로 유지)
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 0 | 조사·설계·계획서 게시 | ✅ 이슈 코멘트 |
| Phase 1 | DB 계약 — 컬럼·검증기·스크럽·RPC 재발행·pgTAP (750000) | ✅ PR #734 (325d5200) |
| Phase 2 | 클라 데이터 계약 — loadPctBase 전 구간 관통 | ✅ PR #734 |
| Phase 3 | 데스크톱 편집기 — % 게이트 일반화·기준 전 종목 선택 | ✅ PR #734 |
| Phase 4 | 모바일 편집기 — 동등 기능 + 복합 % 신설 | ✅ PR #734 |
| Phase 5 | 검증·랜딩 — CI·Production·Vercel 실측 | ✅ 08-25 |
| Phase 6 | 작업 기록 | ✅ 이 문서 |
1. 배경
역도식 프로그래밍은 보조 동작의 무게를 대표 종목 1RM의 %로 쓴다 — "Jerk Dip + Rack Jerk, 70% (2+3)x2"의 70%는 각 동작이 아니라 본인 Clean & Jerk 1RM 기준이다. 그런데 계획 세트의 %는 세 겹으로 자기 종목에 묶여 있었다: ① planned_sets.load_percent는 "무엇의 %인지"를 담는 자리가 없어 암묵적으로 자기 종목 1RM으로만 해석됐고, ② 편집기 % 버튼은 자기 종목(복합=구성 동작) 1RM 존재로 게이트돼 1RM 기록이 없는 종목(Jerk Dip)은 % 입력 자체가 안 보였고, ③ 데스크톱 복합에만 있던 "기준 1RM" 선택(pctBase)은 편집 세션 로컬이라 저장하면 소실됐다. 모바일 복합세트는 무게 단위 토글 자체가 없어 kg 고정이었다.
2. 문제 제기
%의 기준이 스키마에 없다 — 암묵 자기 종목 해석
load_percent는 스칼라 하나. % → kg 해석은 클라이언트 입력 시점에 자기 종목 1RM으로만 일어나고 서버는 1RM을 모른다. 타 종목 기준은 표현 자체가 불가능.
기존 기준 선택(pctBase)은 휘발·복합·데스크톱 한정
DesktopPlanEditorCardParts.tsx의 pctBase는 ① 복합 구성 동작 중에서만 고를 수 있어 제3 종목(C&J) 지정 불가 ② 저장 계약(WriteDto·RPC)에 없어 재편집 시 첫 1RM 보유 동작으로 폴백 ③ 모바일에 없음.
% 게이트가 필요한 유저를 배제
% 버튼 조건이 "자기(구성 동작) 1RM 있음"이라, 1RM을 잴 일이 없는 보조 동작에서는 %를 켤 방법이 없었다 — 이슈의 Jerk Dip이 정확히 이 경우.
3. 해결 방안
원칙 (오너 지시, 2026-08-25)
"땜질식 계획은 하지 말고, 가급적 본질적으로 문제를 해결하고 더 좋은 아키텍처를 구현하는 방향으로" — %의 기준을 스키마의 일급 데이터(provenance)로 승격하고, 기준 선택을 전 종목 일반 프리미티브로 확장. 오너 승인 없이 완주(같은 지시).
접근
| 대안 | 판정 |
|---|---|
composite_meta에 기준 끼워넣기 | 기각 — 복합 한정 땜질, 단일 종목(스내치 풀 @ 스내치 %) 불가, meta는 그룹핑 계층 |
load를 표현식 모델({kind:'percent', of, value})로 재설계 | 기각 — stats_load_kg·통계·세션 시작 전 파이프라인 재설계 대비 지금 필요한 능력 이득 없음 |
세트 행에 기준 컬럼 신설(load_percent_base_exercise_id), kg 파생·정본 계약 유지 | 채택 — 기존 두-스칼라(입력 %+파생 kg) 모델에 provenance 한 칸 추가, null=자기 종목이라 기존 행 전부 불변·백필 0 |
의미론 확정: 기준 1RM이 나중에 바뀌어도 저장된 kg는 자동 변경하지 않는다(작성된 계획의 무게는 명시적 재편집으로만 — 기존 % 동작과 동일 계약). 자기 종목 기준은 서버가 null로 정규화해 기본 상태의 표현을 하나로 유지. 명시 기준의 1RM이 사라지면 %를 잠가 kg 입력으로 폴백 — 다른 1RM으로 조용히 대체 계산하지 않는다.
4. 적용한 내용
Phase 1 — DB 계약 (#734, 20260821750000)
컬럼(uuid 형식·note 행 금지 check, FK 없음 — exercise_id와 같은 취급) + validated_entry_percent_base_v1(형식 22023·존재 23503·자기 종목 null 정규화) + scrub_planned_set_atomic_values_v1 재발행·트리거 재생성(load 원자 해제 또는 % null이면 기준 동반 소거 — apply_plan_atomic_values_v1의 UPDATE도 이 트리거를 지나는 단일 관문이라 apply는 재발행 불요) + validate_plan_payload_v3(+structure_core)·save_plan_v3_engine·get_planned_session_detail_v1_engine 전문 재발행(기준 있는데 % 없으면 22023 거부) + postcheck + pgTAP 12단언.
Phase 2 — 클라 데이터 계약 (#734)
쓰기: 편집 draft exercise.pctBase → 플로우 직렬화기(기준 있을 때만 키 방출 — 바이트 동치 계약 유지) → 평탄화(% 세트에만 loadPctBase, 복합은 그룹 전 행) → WriteDto → savePlan allowlist·uuid 검증·매핑. 읽기: 상세 어댑터 검증 → plannedSetView → 복원에서 그룹/단일 pctBase 재조립(기준 동작 행의 서버 null 정규화 내성 — 그룹 첫 비-null 기준 채택). 타입 미러 3곳 + 직접 select 컬럼.
Phase 3 — 데스크톱 편집기 (#734)
% 게이트 "자기 1RM 있음" → "해석 가능한 기준 1RM 있음(1RM 보유 종목 존재)". DkpBasePick을 복합 동작 택1 → 1RM 보유 전 종목 synonym 검색 선택(검색 계약 준수, 1RM kg 병기, 구성 동작/자기 종목 핀)으로 재구성, 단일 종목에도 기준 행 표시, % placeholder에 기준 종목명 병기.
Phase 4 — 모바일 편집기 (#734)
동등 규칙 + 복합 % 신설(unitMode의 !composite 게이트 해제, 복합 편집에 무게 단위 행 노출 — 디테일·종목 옵션 행은 복합 숨김 유지). WfBasePick(settype 드롭다운 패턴+검색), makeFlowExercise에 pctBase 보존, WorkoutFlow → WfRecord exerciseCatalog 관통. 완료 세션 저장은 명시 키 투영이라 pctBase 무영향(% = 계획 전용, kg 정본 불변).
주요 결정과 그 근거
- 1RM 맵 재사용:
oneRms가 이미 유저 전 종목 UUID 키로 클라에 있어 타 종목 기준에 새 페치 0. - 스크럽 단일 관문: 기준-% 종속을 CHECK 대신 BEFORE 트리거 정규화로 — apply 경로가 %를 null로 바꿔도 제약 위반 대신 기준이 조용히 함께 소거된다.
- 자기 종목 null 정규화: 기본 상태 표현이 하나라 legacy 행·새 행이 같은 의미 공간에 있다.
작업 중 드러난 것
- 번호 선점 2회: 730000(target_muscles)·740000(UGC)이 랜딩 도중 선점 — 장부 실측 후 750000 재번호, pgTAP 시드도 upstream
target_muscles NOT NULL에 맞춰 보정. 740000의 "RPC 일괄 재발행"과 이 트랙 재발행 함수 5종의 겹침 없음을 grep으로 실측(겹쳤다면 게이트 되돌림 사고). - 충돌 PR은 Actions 미발화(기지 함정 재확인): push 후 run이 안 보이면 mergeable부터 확인.
- pgTAP 함정 2건: ① 종목 시드
measurement_type허용값은weight_reps|reps|time|distance|bodyweight_reps— duration 기록 종목은'time'②format()본문의 리터럴%(제목 "기준 % 계획")가 지정자로 해석돼 셋업 크래시 — %%로 이스케이프하거나 문구에서 제거. plannedSessionHardCutoverSqlContract의 추출기는 validate 정의부터 다음$$;까지를 자르므로 마이그레이션 내 섹션 순서를 710000과 동일하게(validate → structure_core) 유지해야 한다.- 레이어 게이트 2건: viewMappers에서
set.load_percent*스네이크 접근 금지(leanModelHardCut) · 복원 입력은 camel 단일 키(check:dual-key 기준선). - 별건 발견(보고만, 미수정): 계획 저장 DTO(
workoutPlanCommands.tsplannedSessionWriteDto)와 플로우 직렬화기 둘 다synonymId를 떨어뜨림 — #673 계획 표기 보존의 클라 층 회귀 의심. 오너 판단 대기.
5. 적용 결과
| 항목 | 전 → 후 |
|---|---|
| 타 종목 1RM 기준 % 작성 | 불가 → 데스크톱·모바일, 단일·복합 모두 가능 (스모크 실측: C&J 120kg 기준 70%→84kg·80%→96kg) |
| 기준 선택 범위 | 복합 구성 동작 한정·데스크톱 한정 → 1RM 보유 전 종목 검색·양 플랫폼 |
| 기준 선택 영속 | 편집 세션 로컬(재열면 소실) → planned_sets 영속·재편집 복원 (하네스·pgTAP 왕복 증명) |
| 1RM 없는 종목의 % | % 버튼 숨김 → 기준 선택으로 사용 가능 |
| 모바일 복합 무게 단위 | kg 고정(토글 없음) → kg/lb/% |
| 기존 행·기존 % 동작 | 의미 불변(null=자기 종목, 백필 0) — 전체 스위트 1944 pass·pgTAP 897단언 통과 |
| Production | 750000 적용·remote-schema 0 실패·컬럼/함수/엔진 3종 프로브 확인·Vercel 신규 마커 실측 |
| 오너 실기기 확인 | 종결 — 오너 종결 지시(08-25)로 이슈 #676 닫음 |
6. 이번 개선으로 향상된 것
역도식 프로그래밍이 계획으로 표현된다
Jerk Dip + Rack Jerk 70%(C&J 기준) 같은 대표 종목 % 처방을 그대로 쓰고, 재편집 때 기준이 유지된다.
%의 기준이 데이터가 됐다
"70%가 무엇의 70%인지"가 스키마에 남는다(provenance) — 이후 % 재해석·표시·분석 기능의 토대. 구조적으로 남는 것: 기준 컬럼·검증기·스크럽 단일 관문·왕복 하네스 케이스·pgTAP.
기준 선택이 일반 프리미티브가 됐다
복합 한정 UI 특례가 전 종목 검색 선택(synonym 계약 재사용)으로 승격 — 단일·복합·양 플랫폼이 같은 규칙 하나를 쓴다.