[
  {
    "rule_id": "r_001",
    "when_state": "met",
    "condition": "실제 반복 >= 목표 반복 이고 통증 표시 없음",
    "delta_kg": 2.5,
    "delta_pct": null,
    "unlock_next": true,
    "requires_user_confirm": false,
    "note": "계획대로 끝냈으면 다음 세트 제안 무게를 2.5kg 올립니다"
  },
  {
    "rule_id": "r_002",
    "when_state": "over_reps",
    "condition": "실제 반복 >= 목표 반복 + 3",
    "delta_kg": 5,
    "delta_pct": null,
    "unlock_next": true,
    "requires_user_confirm": true,
    "note": "여유가 크면 5kg 증량을 제안하되 사용자가 받아야 적용합니다"
  },
  {
    "rule_id": "r_003",
    "when_state": "under_reps",
    "condition": "목표 반복의 60% 이상 80% 미만",
    "delta_kg": 0,
    "delta_pct": null,
    "unlock_next": true,
    "requires_user_confirm": false,
    "note": "다음 세트는 같은 무게로 유지합니다"
  },
  {
    "rule_id": "r_004",
    "when_state": "under_reps",
    "condition": "목표 반복의 60% 미만",
    "delta_kg": null,
    "delta_pct": -10,
    "unlock_next": true,
    "requires_user_confirm": true,
    "note": "10% 감량안을 띄우고 사용자가 받거나 직접 고쳐야 다음 세트가 열립니다"
  },
  {
    "rule_id": "r_005",
    "when_state": "under_weight",
    "condition": "실제 무게 < 목표 무게 이고 반복은 채움",
    "delta_kg": 0,
    "delta_pct": null,
    "unlock_next": true,
    "requires_user_confirm": false,
    "note": "다음 세트 기준을 실제로 든 무게로 바꿔 이어갑니다"
  },
  {
    "rule_id": "r_006",
    "when_state": "pain",
    "condition": "통증 표시가 있는 세트",
    "delta_kg": null,
    "delta_pct": -20,
    "unlock_next": false,
    "requires_user_confirm": true,
    "note": "증량 제안을 만들지 않고 20% 감량안이나 휴식만 내놓으며, 사용자가 고르기 전에는 다음 세트가 열리지 않습니다"
  },
  {
    "rule_id": "r_007",
    "when_state": "pain",
    "condition": "같은 부위 통증 표시가 한 세션에 두 번 이상",
    "delta_kg": null,
    "delta_pct": null,
    "unlock_next": false,
    "requires_user_confirm": true,
    "note": "해당 부위 세트를 더 열지 않고 전문가 상담 안내로 세션을 닫습니다"
  },
  {
    "rule_id": "r_008",
    "when_state": "skipped",
    "condition": "사용자가 건너뛰기를 고른 세트",
    "delta_kg": 0,
    "delta_pct": null,
    "unlock_next": true,
    "requires_user_confirm": false,
    "note": "건너뛴 세트는 기록에 남기되 다음 세트 제안 무게 계산에서 뺍니다"
  },
  {
    "rule_id": "r_009",
    "when_state": "substituted",
    "condition": "기구 점유로 대체 운동을 고른 세트",
    "delta_kg": null,
    "delta_pct": null,
    "unlock_next": true,
    "requires_user_confirm": true,
    "note": "대체 운동은 무게 기준이 달라 제안을 내지 않고 사용자가 첫 무게를 직접 넣어야 합니다"
  },
  {
    "rule_id": "r_010",
    "when_state": "unmapped",
    "condition": "exercise_key 가 없는 운동 표기",
    "delta_kg": null,
    "delta_pct": null,
    "unlock_next": false,
    "requires_user_confirm": true,
    "note": "사용자가 화면에서 운동을 지정하기 전에는 규칙을 적용하지 않고 완료 버튼도 열지 않습니다"
  },
  {
    "rule_id": "r_011",
    "when_state": "missing_log",
    "condition": "무게나 반복 입력 없이 완료를 누른 경우",
    "delta_kg": null,
    "delta_pct": null,
    "unlock_next": false,
    "requires_user_confirm": true,
    "note": "빈 값으로는 확정되지 않고 입력 칸을 붉게 표시해 되돌립니다"
  },
  {
    "rule_id": "r_012",
    "when_state": "met",
    "condition": "맨몸 운동이라 목표 무게가 없음",
    "delta_kg": null,
    "delta_pct": null,
    "unlock_next": true,
    "requires_user_confirm": false,
    "note": "무게 대신 다음 세트 목표 반복을 1회 올립니다"
  },
  {
    "rule_id": "r_013",
    "when_state": "met",
    "condition": "시간으로 재는 운동이라 목표 반복이 없음",
    "delta_kg": null,
    "delta_pct": null,
    "unlock_next": true,
    "requires_user_confirm": false,
    "note": "다음 세트 목표 시간을 5초 올립니다"
  }
]